File: test_restore_from_backup.html

package info (click to toggle)
firefox 147.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,683,320 kB
  • sloc: cpp: 7,607,359; javascript: 6,533,295; ansic: 3,775,223; python: 1,415,500; xml: 634,561; asm: 438,949; java: 186,241; sh: 62,752; makefile: 18,079; objc: 13,092; perl: 12,808; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; php: 436; lisp: 258; awk: 247; sql: 66; sed: 54; csh: 10; exp: 6
file content (531 lines) | stat: -rw-r--r-- 20,149 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Tests for the restore-from-backup component</title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
  <script type="application/javascript" src="head.js"></script>
  <script
  src="chrome://browser/content/backup/restore-from-backup.mjs"
  type="module"
  ></script>
  <link rel="localization" href="browser/backupSettings.ftl"/>
  <link rel="localization" href="branding/brand.ftl"/>
  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
  <script>

    const { BrowserTestUtils } = ChromeUtils.importESModule(
      "resource://testing-common/BrowserTestUtils.sys.mjs"
    );
    const { ERRORS } = ChromeUtils.importESModule(
      "chrome://browser/content/backup/backup-constants.mjs"
    );

    /**
     * Tests that adding a restore-from-backup element to the DOM causes it to
     * fire a BackupUI:InitWidget event.
     */
    add_task(async function test_initWidget() {
      let restoreFromBackup = document.createElement("restore-from-backup");
      let content = document.getElementById("content");

      let sawInitWidget = BrowserTestUtils.waitForEvent(content, "BackupUI:InitWidget");
      content.appendChild(restoreFromBackup);
      await sawInitWidget;
      ok(true, "Saw BackupUI:InitWidget");

      restoreFromBackup.remove();
    });

    /**
     * Tests that pressing the restore and restart button will dispatch the expected events.
     */
    add_task(async function test_restore() {
      let restoreFromBackup = document.getElementById("test-restore-from-backup");
      let confirmButton = restoreFromBackup.confirmButtonEl;

      ok(confirmButton, "Restore button should be found");

      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileToRestore: "/Some/User/Documents/Firefox Backup/backup.html"
      };
      await restoreFromBackup.updateComplete;

      let content = document.getElementById("content");
      let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:RestoreFromBackupFile");

      confirmButton.click();

      await promise;

      ok(true, "Detected event after pressing the restore button");
    });

    /**
     * Tests that pressing the cancel button will dispatch the expected events.
     */
    add_task(async function test_cancel() {
      let restoreFromBackup = document.getElementById("test-restore-from-backup");
      let cancelButton = restoreFromBackup.cancelButtonEl;

      ok(cancelButton, "Cancel button should be found");

      let content = document.getElementById("content");
      let promise = BrowserTestUtils.waitForEvent(content, "dialogCancel");

      cancelButton.click();

      await promise;
      ok(true, "Detected event after pressing the cancel button");
    });

    /**
     * Tests that pressing the choose button will dispatch the expected events.
     */
    add_task(async function test_choose() {
      let restoreFromBackup = document.getElementById("test-restore-from-backup");
      restoreFromBackup.backupServiceState.backupFileToRestore = "/backup/file\\path.html";

      ok(restoreFromBackup.chooseButtonEl, "Choose button should be found");

      let content = document.getElementById("content");
      let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:ShowFilepicker");

      restoreFromBackup.chooseButtonEl.click();

      let event = await promise;
      ok(true, "Detected event after pressing the choose button");
      is(event.detail.win, window.browsingContext, "Current window will be the parent");
      is(event.detail.filter, "filterHTML", "Only HTML files will be shown");
      is(event.detail.existingBackupPath, "/backup/file\\path.html", "Path to the backup file is given");
    });

    /**
     * Tests that selecting a backup file from the filepicker will dispatch the expected events.
     */
    add_task(async function test_new_file_selected() {
      let restoreFromBackup = document.getElementById("test-restore-from-backup");

      let content = document.getElementById("content");
      let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:GetBackupFileInfo");

      let selectEvent = new CustomEvent("BackupUI:SelectNewFilepickerPath", {
        detail: {
          path: "/Some/User/Documents/Firefox Backup/backup-default.html",
          iconURL: "chrome://global/skin/icons/document.svg"
        }
      });
      restoreFromBackup.dispatchEvent(selectEvent);

      await promise;
      ok(true, "Detected event after new file is selected");
    });

    /**
     * Tests that the password input will shown when a file is encrypted.
     */
    add_task(async function test_show_password() {
      let restoreFromBackup = document.getElementById("test-restore-from-backup");

      ok(!restoreFromBackup.passwordInput, "Password input should not be present");

      let date = new Date();
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: {
          date,
          isEncrypted: true,
        }
      };

      await restoreFromBackup.updateComplete;

      ok(restoreFromBackup.passwordInput, "Password input should be present");
    });

    /**
     * Tests that incorrect password is shown in a different error format than top level
     */
    add_task(async function test_incorrect_password_error_condition() {
      let restoreFromBackup = document.getElementById("test-restore-from-backup");

      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.NONE, "Recovery error code should be 0");
      ok(!restoreFromBackup.isIncorrectPassword, "Error message should not be displayed");
      ok(!restoreFromBackup.errorMessageEl, "No error message should be displayed");

      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        recoveryErrorCode: ERRORS.UNAUTHORIZED
      };

      await restoreFromBackup.updateComplete;

      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.UNAUTHORIZED, "Recovery error code should be set");
      ok(restoreFromBackup.isIncorrectPassword, "Error message should be displayed");
      ok(!restoreFromBackup.errorMessageEl, "No top level error message should be displayed");
    });

    /**
     * Tests that a top level error message is displayed if there is an error restoring from backup.
     */
    add_task(async function test_error_condition() {
      let restoreFromBackup = document.getElementById("test-restore-from-backup");

      ok(!restoreFromBackup.errorMessageEl, "No error message should be displayed");

      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        recoveryErrorCode: ERRORS.CORRUPTED_ARCHIVE
      };

      await restoreFromBackup.updateComplete;

      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.CORRUPTED_ARCHIVE, "Recovery error code should be set");
      ok(restoreFromBackup.errorMessageEl, "Error message should be displayed");
    });

    /**
     * Tests that changes to backupServiceState emits BackupUI:RecoveryProgress,
     * with the current progress state and progress is false when an error code is present.
     */
    add_task(async function test_recovery_state_updates() {
      const content = document.getElementById("content");
      let restoreFromBackup = document.getElementById("test-restore-from-backup");
      content.appendChild(restoreFromBackup);

      // Reset previous state changes
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        recoveryInProgress: false,
        recoveryErrorCode: ERRORS.NONE,
      };
      await restoreFromBackup.updateComplete;

      // Helper to dispatch the BackupUI:RecoveryProgress event
      async function sendState(testState) {
        const promise = BrowserTestUtils.waitForEvent(
          restoreFromBackup,
          "BackupUI:RecoveryProgress"
        );
        restoreFromBackup.backupServiceState = {
          ...restoreFromBackup.backupServiceState,
          ...testState,
        };
        await restoreFromBackup.updateComplete;
        return promise;
      }

      // Initial state
      is(
        restoreFromBackup.backupServiceState.recoveryInProgress,
        false,
        "Initial progress state is false"
      );
      is(restoreFromBackup.backupServiceState.recoveryErrorCode, ERRORS.NONE, "Initial error code should be 0");

      // Backup in progress with no error
      let event = await sendState({ recoveryInProgress: true });
      is(event.detail?.recoveryInProgress, true, "'recoveryInProgress' is true");
      is(restoreFromBackup.backupServiceState.recoveryInProgress, true, "State reflects in-progress");

      // Backup not in progress
      event = await sendState({ recoveryInProgress: false, recoveryErrorCode: 0 });
      is(event.detail?.recoveryInProgress, false, "'recoveryInProgress' is false");
      is(
        restoreFromBackup.backupServiceState.recoveryInProgress,
        false,
        "State reflects not in-progress"
      );

      // Any error should clear progress
      for (const code of [ERRORS.CORRUPTED_ARCHIVE, ERRORS.UNAUTHORIZED]) {
        info(`Asserting recovery progress clears with error code: ${code}`);
        event = await sendState({
          recoveryInProgress: true,
          recoveryErrorCode: ERRORS.NONE,
        });
        is(
          restoreFromBackup.backupServiceState.recoveryInProgress,
          true,
          "State reflects in-progress"
        );

        // Add an error
        event = await sendState({
          recoveryInProgress: true,
          recoveryErrorCode: code,
        });
        is(
          event.detail?.recoveryInProgress,
          false,
          `Progress cleared for error ${code}`
        );
        // Clear state
        await sendState({ recoveryInProgress: false, recoveryErrorCode: ERRORS.NONE });
      }
      restoreFromBackup.remove();
    });

    /**
     * Helper function to test that a support link has correct attributes
     * and UTM params when used with aboutWelcomeEmbedded
     *
     * @param {Element} link - The support link element to test
     * @param {string} linkName - The name of the link to test
     */

    function assertEmbeddedSupportLink(link, linkName) {
      ok(link, `${linkName} should be present`);
      ok(
        !link.hasAttribute("is"),
        `${linkName} should not have 'is' attribute`
      );
      ok(
        !link.hasAttribute("support-page"),
        `${linkName} should not have support-page attribute`
      );
      ok(
        link.hasAttribute("href"),
        `${linkName} should have href attribute`
      );

      let url = new URL(link.getAttribute("href"));

      is(
        url.searchParams.get("utm_medium"),
        "firefox-desktop",
        `${linkName} should have correct utm_medium`
      );
      is(
        url.searchParams.get("utm_source"),
        "npo",
        `${linkName} should have correct utm_source`
      );
      is(
        url.searchParams.get("utm_campaign"),
        "fx-backup-restore",
        `${linkName} should have correct utm_campaign`
      );
      is(
        url.searchParams.get("utm_content"),
        "restore-error",
        `${linkName} should have correct utm_content`
      );
      is(
        link.getAttribute("target"),
        "_blank",
        `${linkName} should have target='_blank'`
      );
    }

    /**
     * Tests that support links have UTM parameters when aboutWelcomeEmbedded is true
     */
    add_task(async function test_support_links_with_utm_params() {
      let content = document.getElementById("content");
      let restoreFromBackup = document.createElement("restore-from-backup");
      content.appendChild(restoreFromBackup);

      // Set up the support base link for testing, otherwise links will be broken
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        supportBaseLink: "https://support.mozilla.org/",
      };
      restoreFromBackup.aboutWelcomeEmbedded = true;
      await restoreFromBackup.updateComplete;

      // Test the "no backup file" link
      let noBackupFileLink = restoreFromBackup.shadowRoot.querySelector(
        "#restore-from-backup-no-backup-file-link"
      );
      assertEmbeddedSupportLink(noBackupFileLink, "No backup file link");

      // Test the incorrect password support link
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: {
          date: new Date(),
          isEncrypted: true,
        },
        recoveryErrorCode: ERRORS.UNAUTHORIZED,
      };
      await restoreFromBackup.updateComplete;
      let passwordErrorLink = restoreFromBackup.shadowRoot.querySelector(
        "#backup-incorrect-password-support-link"
      );
      assertEmbeddedSupportLink(passwordErrorLink, "Password error link");

      restoreFromBackup.remove();
    });

    /**
     * Tests that the correct status is displayed under the input
     * for different backup file info states.
     */
    add_task(async function test_backup_file_status_rendering() {
      let content = document.getElementById("content");
      let restoreFromBackup = document.createElement("restore-from-backup");
      content.appendChild(restoreFromBackup);

      // Test that when no backup file is selected, the support link is displayed
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: null,
        recoveryErrorCode: ERRORS.NONE,
      };
      await restoreFromBackup.updateComplete;

      let noBackupLink = restoreFromBackup.shadowRoot.querySelector(
        "#restore-from-backup-no-backup-file-link"
      );
      ok(noBackupLink, "Should show support link when no backup file is selected");

      let backupInfo = restoreFromBackup.shadowRoot.querySelector(
        "#restore-from-backup-backup-found-info"
      );
      ok(!backupInfo, "Should not show backup info when no backup file is selected");

      // Test that when a backup file is selected, the backup info is displayed
      const IS_ENCRYPTED = true;
      const DATE = new Date("2024-01-01T00:00:00.000Z");
      const DEVICE_NAME = "test-device";

      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: {
          isEncrypted: IS_ENCRYPTED,
          date: DATE,
          deviceName: DEVICE_NAME
        },
        recoveryErrorCode: ERRORS.NONE,
      };
      await restoreFromBackup.updateComplete;

      noBackupLink = restoreFromBackup.shadowRoot.querySelector(
        "#restore-from-backup-no-backup-file-link"
      );
      ok(!noBackupLink, "Should not show support link when backup file is found");

      backupInfo = restoreFromBackup.shadowRoot.querySelector(
        "#restore-from-backup-backup-found-info"
      );
      ok(backupInfo, "Should show backup info when backup file is found");
      is(backupInfo.getAttribute("data-l10n-id"), "backup-file-creation-date-and-device",
        "Should have correct l10n id for backup info");

      // Test that when embedded in about:welcome, if an error occurs,
      // the generic file error template is displayed
      restoreFromBackup.aboutWelcomeEmbedded = true;
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: null,
        recoveryErrorCode: ERRORS.CORRUPTED_ARCHIVE,
      };
      await restoreFromBackup.updateComplete;

      let errorTemplate = restoreFromBackup.shadowRoot.querySelector(
        "#backup-generic-file-error"
      );
      ok(errorTemplate, "Should show error template in embedded context with error");

      // Test that when not embedded in about:welcome, if an error occurs,
      // the support link is displayed instead of the generic file error template
      restoreFromBackup.aboutWelcomeEmbedded = false;
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: null,
        recoveryErrorCode: ERRORS.FILE_SYSTEM_ERROR,
      };
      await restoreFromBackup.updateComplete;

      errorTemplate = restoreFromBackup.shadowRoot.querySelector(
        "#backup-generic-file-error"
      );
      ok(!errorTemplate, "Should not show generic file error template when not embedded in about:welcome");

      noBackupLink = restoreFromBackup.shadowRoot.querySelector(
        "#restore-from-backup-no-backup-file-link"
      );
      ok(noBackupLink, "Should show support link when not embedded in about:welcome, with error");

      restoreFromBackup.remove();
    });

    /**
     * Tests that when embedded in about:welcome, the textarea's aria-describedby
     * attribute correctly references the appropriate element based on the displayed status.
     */
    add_task(async function test_textarea_aria_describedby_accessibility() {
      let content = document.getElementById("content");
      let restoreFromBackup = document.createElement("restore-from-backup");
      content.appendChild(restoreFromBackup);

      restoreFromBackup.aboutWelcomeEmbedded = true;
      await restoreFromBackup.updateComplete;

      let textarea = restoreFromBackup.shadowRoot.querySelector("#backup-filepicker-input");
      ok(textarea, "Textarea should be present when aboutWelcomeEmbedded is true");

      // Test that when there is no backup file info, we should reference no-backup-file-link
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: null,
        recoveryErrorCode: ERRORS.NONE,
      };
      await restoreFromBackup.updateComplete;

      let ariaDescribedBy = textarea.getAttribute("aria-describedby");
      is(ariaDescribedBy, "restore-from-backup-no-backup-file-link",
        "aria-describedby should reference no-backup-file-link when no backup info");

      let referencedElement = restoreFromBackup.shadowRoot.querySelector("#restore-from-backup-no-backup-file-link");
      ok(referencedElement, "Referenced element should exist");

      // Test that when a backup file is found, we should reference backup-found-info
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: {
          date: new Date(),
          deviceName: "test-device",
          isEncrypted: false,
        },
        recoveryErrorCode: ERRORS.NONE,
      };
      await restoreFromBackup.updateComplete;

      ariaDescribedBy = textarea.getAttribute("aria-describedby");
      is(ariaDescribedBy, "restore-from-backup-backup-found-info",
        "aria-describedby should reference backup-found-info when backup file is found");

      referencedElement = restoreFromBackup.shadowRoot.querySelector("#restore-from-backup-backup-found-info");
      ok(referencedElement, "Referenced element should exist");

      // Test that when we're in an error state, we should reference the generic-file-error
      restoreFromBackup.backupServiceState = {
        ...restoreFromBackup.backupServiceState,
        backupFileInfo: null,
        recoveryErrorCode: ERRORS.CORRUPTED_ARCHIVE,
      };
      await restoreFromBackup.updateComplete;

      ariaDescribedBy = textarea.getAttribute("aria-describedby");
      is(ariaDescribedBy, "backup-generic-file-error",
        "aria-describedby should reference generic-file-error when there's a recovery error");

      referencedElement = restoreFromBackup.shadowRoot.querySelector("#backup-generic-file-error");
      ok(referencedElement, "Referenced element should exist");

      restoreFromBackup.remove();
    });
  </script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
  <restore-from-backup id="test-restore-from-backup"></restore-from-backup>
</div>
<pre id="test"></pre>
</body>
</html>