summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/test/browser_storage_recovery.js
blob: 5ecc1350216fda359bac4c9e9c4d7d6c1539552b (plain)
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
"use strict";

// This test registers a SW for a scope that will never control a document
// and therefore never trigger a "fetch" functional event that would
// automatically attempt to update the registration.  The overlap of the
// PAGE_URI and SCOPE is incidental.  checkForUpdate is the only thing that
// will trigger an update of the registration and so there is no need to
// worry about Schedule Job races to coalesce an update job.

const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/";
const PAGE_URI = BASE_URI + "empty.html";
const SCOPE = PAGE_URI + "?storage_recovery";
const SW_SCRIPT = BASE_URI + "storage_recovery_worker.sjs";

async function checkForUpdate(browser) {
  return SpecialPowers.spawn(browser, [SCOPE], async function(uri) {
    let reg = await content.navigator.serviceWorker.getRegistration(uri);
    await reg.update();
    return !!reg.installing;
  });
}

// Delete all of our chrome-namespace Caches for this origin, leaving any
// content-owned caches in place.  This is exclusively for simulating loss
// of the origin's storage without loss of the registration and without
// having to worry that future enhancements to QuotaClients/ServiceWorkerRegistrar
// will break this test.  If you want to wipe storage for an origin, use
// QuotaManager APIs
async function wipeStorage(u) {
  let uri = Services.io.newURI(u);
  let principal = Services.scriptSecurityManager.createContentPrincipal(
    uri,
    {}
  );
  let caches = new CacheStorage("chrome", principal);
  let list = await caches.keys();
  return Promise.all(list.map(c => caches.delete(c)));
}

add_setup(async function() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["dom.serviceWorkers.enabled", true],
      ["dom.serviceWorkers.testing.enabled", true],
      ["dom.serviceWorkers.idle_timeout", 0],
    ],
  });

  // Configure the server script to not redirect.
  await fetch(SW_SCRIPT + "?clear-redirect");

  let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  await SpecialPowers.spawn(
    browser,
    [{ script: SW_SCRIPT, scope: SCOPE }],
    async function(opts) {
      let reg = await content.navigator.serviceWorker.register(opts.script, {
        scope: opts.scope,
      });
      let worker = reg.installing || reg.waiting || reg.active;
      await new Promise(resolve => {
        if (worker.state === "activated") {
          resolve();
          return;
        }
        worker.addEventListener("statechange", function onStateChange() {
          if (worker.state === "activated") {
            worker.removeEventListener("statechange", onStateChange);
            resolve();
          }
        });
      });
    }
  );

  BrowserTestUtils.removeTab(tab);
});

// Verify that our service worker doesn't update normally.
add_task(async function normal_update_check() {
  let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let updated = await checkForUpdate(browser);
  ok(!updated, "normal update check should not trigger an update");

  BrowserTestUtils.removeTab(tab);
});

// Test what happens when we wipe the service worker scripts
// out from under the site before triggering the update.  This
// should cause an update to occur.
add_task(async function wiped_update_check() {
  // Wipe the backing cache storage, but leave the SW registered.
  await wipeStorage(PAGE_URI);

  let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  let updated = await checkForUpdate(browser);
  ok(updated, "wiping the service worker scripts should trigger an update");

  BrowserTestUtils.removeTab(tab);
});

// Test what happens when we wipe the service worker scripts
// out from under the site before triggering the update.  This
// should cause an update to occur.
add_task(async function wiped_and_failed_update_check() {
  // Wipe the backing cache storage, but leave the SW registered.
  await wipeStorage(PAGE_URI);

  // Configure the service worker script to redirect.  This will
  // prevent the update from completing successfully.
  await fetch(SW_SCRIPT + "?set-redirect");

  let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
  let browser = gBrowser.getBrowserForTab(tab);
  await BrowserTestUtils.browserLoaded(browser);

  // Attempt to update the service worker.  This should throw
  // an error because the script is now redirecting.
  let updateFailed = false;
  try {
    await checkForUpdate(browser);
  } catch (e) {
    updateFailed = true;
  }
  ok(updateFailed, "redirecting service worker script should fail to update");

  // Also, since the existing service worker's scripts are broken
  // we should also remove the registration completely when the
  // update fails.
  let exists = await SpecialPowers.spawn(browser, [SCOPE], async function(uri) {
    let reg = await content.navigator.serviceWorker.getRegistration(uri);
    return !!reg;
  });
  ok(
    !exists,
    "registration should be removed after scripts are wiped and update fails"
  );

  // Note, we don't have to clean up the service worker registration
  // since its effectively been force-removed here.

  BrowserTestUtils.removeTab(tab);
});