summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js
blob: 8e8e79c2c769026692dce1fb8f28998705d3afb2 (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
"use strict";

/**
 * This file contains test for 'theme' type WebExtension addons. Tests focus mostly
 * on interoperability between the different theme formats (XUL and LWT) and
 * Addon Manager integration.
 *
 * Coverage may overlap with other tests in this folder.
 */

const THEME_IDS = [
  "theme3@tests.mozilla.org",
  "theme2@personas.mozilla.org", // Unused. Legacy. Evil.
  "default-theme@mozilla.org",
];
const REAL_THEME_IDS = [THEME_IDS[0], THEME_IDS[2]];
const DEFAULT_THEME = THEME_IDS[2];

const profileDir = gProfD.clone();
profileDir.append("extensions");

Services.prefs.setIntPref(
  "extensions.enabledScopes",
  AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION
);

// We remember the last/ currently active theme for tracking events.
var gActiveTheme = null;

add_task(async function setup_to_default_browserish_state() {
  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");

  await promiseWriteWebManifestForExtension(
    {
      author: "Some author",
      manifest_version: 2,
      name: "Web Extension Name",
      version: "1.0",
      theme: { images: { theme_frame: "example.png" } },
      browser_specific_settings: {
        gecko: {
          id: THEME_IDS[0],
        },
      },
    },
    profileDir
  );

  await promiseStartupManager();

  if (AppConstants.MOZ_DEV_EDITION) {
    // Developer Edition selects the wrong theme by default.
    let defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME);
    await defaultTheme.enable();
  }

  let [t1, t2, d] = await promiseAddonsByIDs(THEME_IDS);
  Assert.ok(t1, "Theme addon should exist");
  Assert.equal(t2, null, "Theme addon is not a thing anymore");
  Assert.ok(d, "Theme addon should exist");

  await t1.disable();
  await new Promise(executeSoon);
  Assert.ok(!t1.isActive, "Theme should be disabled");
  Assert.ok(d.isActive, "Default theme should be active");

  await promiseRestartManager();

  [t1, t2, d] = await promiseAddonsByIDs(THEME_IDS);
  Assert.ok(!t1.isActive, "Theme should still be disabled");
  Assert.ok(d.isActive, "Default theme should still be active");

  gActiveTheme = d.id;
});

/**
 * Set the `userDisabled` property of one specific theme and check if the theme
 * switching works as expected by checking the state of all installed themes.
 *
 * @param {String}  which    ID of the addon to set the `userDisabled` property on
 * @param {Boolean} disabled Flag value to switch to
 */
async function setDisabledStateAndCheck(which, disabled = false) {
  if (disabled) {
    Assert.equal(which, gActiveTheme, "Only the active theme can be disabled");
  }

  let themeToDisable = disabled ? which : gActiveTheme;
  let themeToEnable = disabled ? DEFAULT_THEME : which;

  let expectedStates = {
    [themeToDisable]: true,
    [themeToEnable]: false,
  };
  let addonEvents = {
    [themeToDisable]: [{ event: "onDisabling" }, { event: "onDisabled" }],
    [themeToEnable]: [{ event: "onEnabling" }, { event: "onEnabled" }],
  };

  // Set the state of the theme to change.
  let theme = await promiseAddonByID(which);
  await expectEvents({ addonEvents }, () => {
    if (disabled) {
      theme.disable();
    } else {
      theme.enable();
    }
  });

  let isDisabled;
  for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
    isDisabled = theme.id in expectedStates ? expectedStates[theme.id] : true;
    Assert.equal(
      theme.userDisabled,
      isDisabled,
      `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`
    );
    Assert.equal(
      theme.pendingOperations,
      AddonManager.PENDING_NONE,
      "There should be no pending operations when no restart is expected"
    );
    Assert.equal(
      theme.isActive,
      !isDisabled,
      `Theme '${theme.id} should be ${isDisabled ? "in" : ""}active`
    );
  }

  await promiseRestartManager();

  // All should still be good after a restart of the Addon Manager.
  for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
    isDisabled = theme.id in expectedStates ? expectedStates[theme.id] : true;
    Assert.equal(
      theme.userDisabled,
      isDisabled,
      `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`
    );
    Assert.equal(
      theme.isActive,
      !isDisabled,
      `Theme '${theme.id}' should be ${isDisabled ? "in" : ""}active`
    );
    Assert.equal(
      theme.pendingOperations,
      AddonManager.PENDING_NONE,
      "There should be no pending operations left"
    );
    if (!isDisabled) {
      gActiveTheme = theme.id;
    }
  }
}

add_task(async function test_WebExtension_themes() {
  // Enable the WebExtension theme.
  await setDisabledStateAndCheck(THEME_IDS[0]);

  // Disabling WebExtension should revert to the default theme.
  await setDisabledStateAndCheck(THEME_IDS[0], true);

  // Enable it again.
  await setDisabledStateAndCheck(THEME_IDS[0]);
});

add_task(async function test_default_theme() {
  // Explicitly enable the default theme.
  await setDisabledStateAndCheck(DEFAULT_THEME);

  // Swith to the WebExtension theme.
  await setDisabledStateAndCheck(THEME_IDS[0]);

  // Enable it again.
  await setDisabledStateAndCheck(DEFAULT_THEME);
});

add_task(async function uninstall_offers_undo() {
  let defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME);
  const ID = THEME_IDS[0];
  let theme = await promiseAddonByID(ID);

  Assert.ok(theme, "Webextension theme is present");

  async function promiseAddonEvent(event, id) {
    let [addon] = await AddonTestUtils.promiseAddonEvent(event);
    if (id) {
      Assert.equal(addon.id, id, `Got event for expected addon (${event})`);
    }
  }

  async function uninstallTheme() {
    let uninstallingPromise = promiseAddonEvent("onUninstalling", ID);
    await theme.uninstall(true);
    await uninstallingPromise;

    Assert.ok(
      hasFlag(theme.pendingOperations, AddonManager.PENDING_UNINSTALL),
      "Theme being uninstalled has PENDING_UNINSTALL flag"
    );
  }

  async function cancelUninstallTheme() {
    let cancelPromise = promiseAddonEvent("onOperationCancelled", ID);
    theme.cancelUninstall();
    await cancelPromise;

    Assert.equal(
      theme.pendingOperations,
      AddonManager.PENDING_NONE,
      "PENDING_UNINSTALL flag is cleared when uninstall is canceled"
    );
  }

  // A theme should still be disabled if the uninstallation of a disabled theme
  // is undone.
  Assert.ok(!theme.isActive, "Webextension theme is not active");
  Assert.ok(defaultTheme.isActive, "Default theme is active");
  await uninstallTheme();
  await cancelUninstallTheme();
  Assert.ok(!theme.isActive, "Webextension theme is still not active");
  Assert.ok(defaultTheme.isActive, "Default theme is still active");

  // Enable theme, the previously active theme should be disabled.
  await Promise.all([
    promiseAddonEvent("onDisabled", DEFAULT_THEME),
    promiseAddonEvent("onEnabled", ID),
    theme.enable(),
  ]);
  Assert.ok(theme.isActive, "Webextension theme is active after enabling");
  Assert.ok(!defaultTheme.isActive, "Default theme is not active any more");

  // Uninstall active theme, default theme should become active.
  await Promise.all([
    // Note: no listener for onDisabled & ID because the uninstall is pending.
    promiseAddonEvent("onEnabled", DEFAULT_THEME),
    uninstallTheme(),
  ]);
  Assert.ok(!theme.isActive, "Webextension theme is not active upon uninstall");
  Assert.ok(defaultTheme.isActive, "Default theme is active again");

  // Undo uninstall, default theme should be deactivated.
  await Promise.all([
    // Note: no listener for onEnabled & ID because the uninstall was pending.
    promiseAddonEvent("onDisabled", DEFAULT_THEME),
    cancelUninstallTheme(),
  ]);
  Assert.ok(theme.isActive, "Webextension theme is active upon undo uninstall");
  Assert.ok(!defaultTheme.isActive, "Default theme is not active again");

  // Immediately remove the theme. Default theme should be activated.
  await Promise.all([
    promiseAddonEvent("onEnabled", DEFAULT_THEME),
    theme.uninstall(),
  ]);

  await promiseRestartManager();
});

// Test that default_locale works with WE themes
add_task(async function default_locale_themes() {
  let addon = await promiseInstallWebExtension({
    manifest: {
      default_locale: "en",
      name: "__MSG_name__",
      description: "__MSG_description__",
      theme: {
        colors: {
          frame: "black",
          tab_background_text: "white",
        },
      },
    },
    files: {
      "_locales/en/messages.json": `{
        "name": {
          "message": "the name"
        },
        "description": {
          "message": "the description"
        }
      }`,
    },
  });

  addon = await promiseAddonByID(addon.id);
  equal(addon.name, "the name");
  equal(addon.description, "the description");
  equal(addon.type, "theme");
  await addon.uninstall();
});

add_task(async function test_theme_update() {
  let addon = await AddonManager.getAddonByID(DEFAULT_THEME);
  ok(!addon.userDisabled, "default theme is enabled");

  await AddonTestUtils.promiseRestartManager("2");

  addon = await AddonManager.getAddonByID(DEFAULT_THEME);
  ok(!addon.userDisabled, "default theme is enabled after upgrade");
});

add_task(async function test_builtin_theme_permissions() {
  const ADDON_ID = "mytheme@mozilla.org";

  let themeDef = {
    manifest: {
      browser_specific_settings: { gecko: { id: ADDON_ID } },
      version: "1.0",
      theme: {},
    },
  };

  function checkPerms(addon) {
    // builtin themes enable or disable based on disabled state
    Assert.equal(
      addon.userDisabled,
      hasFlag(addon.permissions, AddonManager.PERM_CAN_ENABLE),
      "enable permission is correct"
    );
    Assert.equal(
      !addon.userDisabled,
      hasFlag(addon.permissions, AddonManager.PERM_CAN_DISABLE),
      "disable permission is correct"
    );
    // builtin themes do not get any other permission
    Assert.ok(
      !hasFlag(addon.permissions, AddonManager.PERM_CAN_INSTALL),
      "cannot install by user"
    );
    Assert.ok(
      !hasFlag(addon.permissions, AddonManager.PERM_CAN_UPGRADE),
      "cannot upgrade"
    );
    Assert.ok(
      !hasFlag(addon.permissions, AddonManager.PERM_CAN_UNINSTALL),
      "cannot uninstall"
    );
    Assert.ok(
      !hasFlag(
        addon.permissions,
        AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
      ),
      "can change private browsing access"
    );
    Assert.ok(
      hasFlag(addon.permissions, AddonManager.PERM_API_CAN_UNINSTALL),
      "can uninstall via API"
    );
  }

  await setupBuiltinExtension(themeDef, "first-loc", false);
  await AddonManager.maybeInstallBuiltinAddon(
    ADDON_ID,
    "1.0",
    "resource://first-loc/"
  );

  let addon = await AddonManager.getAddonByID(ADDON_ID);
  checkPerms(addon);
  await addon.enable();
  checkPerms(addon);

  await addon.uninstall();
});