summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_clear_cached_resources.js
blob: e4f9f1d40b71d1abd420e6cab45f6ffe32df74eb (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
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
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */

"use strict";

Services.prefs.setBoolPref("extensions.blocklist.enabled", false);

AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
AddonTestUtils.createAppInfo(
  "xpcshell@tests.mozilla.org",
  "XPCShell",
  "1",
  "43"
);

const { AddonManager } = ChromeUtils.importESModule(
  "resource://gre/modules/AddonManager.sys.mjs"
);

const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";

const BASE64_R_PIXEL =
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2P4z8DwHwAFAAH/F1FwBgAAAABJRU5ErkJggg==";
const BASE64_G_PIXEL =
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2Ng+M/wHwAEAQH/7yMK/gAAAABJRU5ErkJggg==";
const BASE64_B_PIXEL =
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYPj/HwADAgH/eL9GtQAAAABJRU5ErkJggg==";

const toArrayBuffer = b64data =>
  Uint8Array.from(atob(b64data), c => c.charCodeAt(0));
const IMAGE_RED = toArrayBuffer(BASE64_R_PIXEL).buffer;
const IMAGE_GREEN = toArrayBuffer(BASE64_G_PIXEL).buffer;
const IMAGE_BLUE = toArrayBuffer(BASE64_B_PIXEL).buffer;

const RGB_RED = "rgb(255, 0, 0)";
const RGB_GREEN = "rgb(0, 255, 0)";
const RGB_BLUE = "rgb(0, 0, 255)";

const CSS_RED_BG = `body { background-color: ${RGB_RED}; }`;
const CSS_GREEN_BG = `body { background-color: ${RGB_GREEN}; }`;
const CSS_BLUE_BG = `body { background-color: ${RGB_BLUE}; }`;

const ADDON_ID = "test-cached-resources@test";

const manifest = {
  version: "1",
  browser_specific_settings: { gecko: { id: ADDON_ID } },
};

const files = {
  "extpage.html": `<!DOCTYPE html>
     <html>
       <head>
         <link rel="stylesheet" href="extpage.css">
       </head>
       <body>
         <img id="test-image" src="image.png">
       </body>
     </html>
  `,
  "other_extpage.html": `<!DOCTYPE html>
     <html>
       <body>
       </body>
     </html>
  `,
  "extpage.css": CSS_RED_BG,
  "image.png": IMAGE_RED,
};

const getBackgroundColor = () => {
  return this.content.getComputedStyle(this.content.document.body)
    .backgroundColor;
};

const hasCachedImage = imgUrl => {
  const { document } = this.content;

  const imageCache = Cc["@mozilla.org/image/tools;1"]
    .getService(Ci.imgITools)
    .getImgCacheForDocument(document);

  const imgCacheProps = imageCache.findEntryProperties(
    Services.io.newURI(imgUrl),
    document
  );

  // return true if the image was in the cache.
  return !!imgCacheProps;
};

const getImageColor = () => {
  const { document } = this.content;
  const img = document.querySelector("img#test-image");
  const canvas = document.createElement("canvas");
  canvas.width = 1;
  canvas.height = 1;
  const ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0); // Draw without scaling.
  const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
  if (a < 1) {
    return `rgba(${r}, ${g}, ${b}, ${a})`;
  }
  return `rgb(${r}, ${g}, ${b})`;
};

async function assertBackgroundColor(page, color, message) {
  equal(
    await page.spawn([], getBackgroundColor),
    color,
    `Got the expected ${message}`
  );
}

async function assertImageColor(page, color, message) {
  equal(await page.spawn([], getImageColor), color, message);
}

async function assertImageCached(page, imageUrl, message) {
  ok(await page.spawn([imageUrl], hasCachedImage), message);
}

// This test verifies that cached css are cleared across addon upgrades and downgrades
// for permanently installed addon (See Bug 1746841).
add_task(async function test_cached_resources_cleared_across_addon_updates() {
  await AddonTestUtils.promiseStartupManager();

  const extension = ExtensionTestUtils.loadExtension({
    useAddonManager: "permanent",
    manifest,
    files,
  });

  await extension.startup();
  equal(
    extension.version,
    "1",
    "Got the expected version for the initial extension"
  );

  const url = extension.extension.baseURI.resolve("extpage.html");
  let page = await ExtensionTestUtils.loadContentPage(url);
  await assertBackgroundColor(
    page,
    RGB_RED,
    "background color (initial extension version)"
  );
  await assertImageColor(page, RGB_RED, "image (initial extension version)");

  info("Verify extension page css and image after addon upgrade");

  await extension.upgrade({
    useAddonManager: "permanent",
    manifest: {
      ...manifest,
      version: "2",
    },
    files: {
      ...files,
      "extpage.css": CSS_GREEN_BG,
      "image.png": IMAGE_GREEN,
    },
  });
  equal(
    extension.version,
    "2",
    "Got the expected version for the upgraded extension"
  );

  await page.loadURL(url);

  await assertBackgroundColor(
    page,
    RGB_GREEN,
    "background color (upgraded extension version)"
  );
  await assertImageColor(page, RGB_GREEN, "image (upgraded extension version)");

  info("Verify extension page css and image after addon downgrade");

  await extension.upgrade({
    useAddonManager: "permanent",
    manifest,
    files,
  });
  equal(
    extension.version,
    "1",
    "Got the expected version for the downgraded extension"
  );

  await page.loadURL(url);

  await assertBackgroundColor(
    page,
    RGB_RED,
    "background color (downgraded extension version)"
  );
  await assertImageColor(
    page,
    RGB_RED,
    "image color (downgraded extension version)"
  );

  await page.close();
  await extension.unload();
  await AddonTestUtils.promiseShutdownManager();
});

// This test verifies that cached css are cleared if we are installing a new
// extension and we did not clear the cache for a previous one with the same uuid
// when it was uninstalled (See Bug 1746841).
add_task(async function test_cached_resources_cleared_on_addon_install() {
  // Make sure the test addon installed without an AddonManager addon wrapper
  // and the ones installed right after that using the AddonManager will share
  // the same uuid (and so also the same moz-extension resource urls).
  Services.prefs.setBoolPref(LEAVE_UUID_PREF, true);
  registerCleanupFunction(() => Services.prefs.clearUserPref(LEAVE_UUID_PREF));

  await AddonTestUtils.promiseStartupManager();

  const nonAOMExtension = ExtensionTestUtils.loadExtension({
    manifest,
    files: {
      ...files,
      // Override css with a different color from the one expected
      // later in this test case.
      "extpage.css": CSS_BLUE_BG,
      "image.png": IMAGE_BLUE,
    },
  });

  await nonAOMExtension.startup();
  equal(
    await AddonManager.getAddonByID(ADDON_ID),
    null,
    "No AOM addon wrapper found as expected"
  );
  let url = nonAOMExtension.extension.baseURI.resolve("extpage.html");
  let page = await ExtensionTestUtils.loadContentPage(url);
  await assertBackgroundColor(
    page,
    RGB_BLUE,
    "background color (addon installed without uninstall observer)"
  );
  await assertImageColor(
    page,
    RGB_BLUE,
    "image (addon uninstalled without clearing cache)"
  );

  // NOTE: unloading a test extension that does not have an AddonManager addon wrapper
  // does not trigger the uninstall observer, and this is what this test needs to make
  // sure that if the cached resources were not cleared on uninstall, then we will still
  // clear it when a newly installed addon is installed even if the two extensions
  // are sharing the same addon uuid (and so also the same moz-extension resource urls).
  await nonAOMExtension.unload();

  const extension = ExtensionTestUtils.loadExtension({
    useAddonManager: "permanent",
    manifest,
    files,
  });

  await extension.startup();
  await page.loadURL(url);

  await assertBackgroundColor(
    page,
    RGB_RED,
    "background color (newly installed addon, same addon id)"
  );
  await assertImageColor(
    page,
    RGB_RED,
    "image (newly installed addon, same addon id)"
  );

  await page.close();
  await extension.unload();
  await AddonTestUtils.promiseShutdownManager();
});

// This test verifies that reloading a temporarily installed addon after
// changing a css file cached in a previous run clears the previously
// cached css and uses the new one changed on disk (See Bug 1746841).
add_task(
  async function test_cached_resources_cleared_on_temporary_addon_reload() {
    await AddonTestUtils.promiseStartupManager();

    const xpi = AddonTestUtils.createTempWebExtensionFile({
      manifest,
      files,
    });

    // This temporary directory is going to be removed from the
    // cleanup function, but also make it unique as we do for the
    // other temporary files (e.g. like getTemporaryFile as defined
    // in XPInstall.jsm).
    const random = Math.round(Math.random() * 36 ** 3).toString(36);
    const tmpDirName = `xpcshelltest_unpacked_addons_${random}`;
    let tmpExtPath = FileUtils.getDir("TmpD", [tmpDirName], true);
    registerCleanupFunction(() => {
      tmpExtPath.remove(true);
    });

    // Unpacking the xpi file into the temporary directory.
    const extDir = await AddonTestUtils.manuallyInstall(
      xpi,
      tmpExtPath,
      null,
      /* unpacked */ true
    );

    let extension = ExtensionTestUtils.expectExtension(ADDON_ID);
    await AddonManager.installTemporaryAddon(extDir);
    await extension.awaitStartup();

    equal(
      extension.version,
      "1",
      "Got the expected version for the initial extension"
    );

    const url = extension.extension.baseURI.resolve("extpage.html");
    let page = await ExtensionTestUtils.loadContentPage(url);
    await assertBackgroundColor(
      page,
      RGB_RED,
      "background color (initial extension version)"
    );
    await assertImageColor(page, RGB_RED, "image (initial extension version)");

    info("Verify updated extension page css and image after addon reload");

    const targetCSSFile = extDir.clone();
    targetCSSFile.append("extpage.css");
    ok(
      targetCSSFile.exists(),
      `Found the ${targetCSSFile.path} target file on disk`
    );
    await IOUtils.writeUTF8(targetCSSFile.path, CSS_GREEN_BG);

    const targetPNGFile = extDir.clone();
    targetPNGFile.append("image.png");
    ok(
      targetPNGFile.exists(),
      `Found the ${targetPNGFile.path} target file on disk`
    );
    await IOUtils.write(targetPNGFile.path, toArrayBuffer(BASE64_G_PIXEL));

    const addon = await AddonManager.getAddonByID(ADDON_ID);
    ok(addon, "Got an AddonWrapper for the test extension");
    await addon.reload();

    await page.loadURL(url);

    await assertBackgroundColor(
      page,
      RGB_GREEN,
      "background (updated files on disk)"
    );
    await assertImageColor(page, RGB_GREEN, "image (updated files on disk)");

    await page.close();
    await addon.uninstall();
    await AddonTestUtils.promiseShutdownManager();
  }
);

// This test verifies that cached images are not cleared between
// permanently installed addon reloads.
add_task(async function test_cached_image_kept_on_permanent_addon_restarts() {
  await AddonTestUtils.promiseStartupManager();
  const extension = ExtensionTestUtils.loadExtension({
    useAddonManager: "permanent",
    manifest,
    files,
  });

  await extension.startup();

  equal(
    extension.version,
    "1",
    "Got the expected version for the initial extension"
  );

  const imageUrl = extension.extension.baseURI.resolve("image.png");
  const url = extension.extension.baseURI.resolve("extpage.html");

  let page = await ExtensionTestUtils.loadContentPage(url);
  await assertBackgroundColor(
    page,
    RGB_RED,
    "background color (first startup)"
  );
  await assertImageColor(page, RGB_RED, "image (first startup)");
  await assertImageCached(page, imageUrl, "image cached (first startup)");

  info("Reload the AddonManager to simulate browser restart");
  extension.setRestarting();
  await AddonTestUtils.promiseRestartManager();
  await extension.awaitStartup();

  await page.loadURL(extension.extension.baseURI.resolve("other_extpage.html"));
  await assertImageCached(
    page,
    imageUrl,
    "image still cached after AddonManager restart"
  );

  await page.close();
  await extension.unload();
  await AddonTestUtils.promiseShutdownManager();
});