tor-browser

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

browser_extension_update_background.js (9484B)


      1 const { AddonManagerPrivate } = ChromeUtils.importESModule(
      2  "resource://gre/modules/AddonManager.sys.mjs"
      3 );
      4 
      5 const { AddonTestUtils } = ChromeUtils.importESModule(
      6  "resource://testing-common/AddonTestUtils.sys.mjs"
      7 );
      8 
      9 AddonTestUtils.initMochitest(this);
     10 AddonTestUtils.hookAMTelemetryEvents();
     11 
     12 const ID = "update2@tests.mozilla.org";
     13 const ID_ICON = "update_icon2@tests.mozilla.org";
     14 const ID_PERMS = "update_perms@tests.mozilla.org";
     15 const ID_LEGACY = "legacy_update@tests.mozilla.org";
     16 const FAKE_INSTALL_TELEMETRY_SOURCE = "fake-install-source";
     17 
     18 requestLongerTimeout(2);
     19 
     20 function promiseViewLoaded(tab, viewid) {
     21  let win = tab.linkedBrowser.contentWindow;
     22  if (
     23    win.gViewController &&
     24    !win.gViewController.isLoading &&
     25    win.gViewController.currentViewId == viewid
     26  ) {
     27    return Promise.resolve();
     28  }
     29 
     30  return waitAboutAddonsViewLoaded(win.document);
     31 }
     32 
     33 function getBadgeStatus() {
     34  let menuButton = document.getElementById("PanelUI-menu-button");
     35  return menuButton.getAttribute("badge-status");
     36 }
     37 
     38 // Set some prefs that apply to all the tests in this file
     39 add_setup(async function () {
     40  await SpecialPowers.pushPrefEnv({
     41    set: [
     42      // We don't have pre-pinned certificates for the local mochitest server
     43      ["extensions.install.requireBuiltInCerts", false],
     44      ["extensions.update.requireBuiltInCerts", false],
     45    ],
     46  });
     47 
     48  // Navigate away from the initial page so that about:addons always
     49  // opens in a new tab during tests
     50  BrowserTestUtils.startLoadingURIString(
     51    gBrowser.selectedBrowser,
     52    "about:robots"
     53  );
     54  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
     55 
     56  registerCleanupFunction(async function () {
     57    // Return to about:blank when we're done
     58    BrowserTestUtils.startLoadingURIString(
     59      gBrowser.selectedBrowser,
     60      "about:blank"
     61    );
     62    await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, {
     63      wantLoad: "about:blank",
     64    });
     65  });
     66 });
     67 
     68 // Helper function to test background updates.
     69 async function backgroundUpdateTest(url, id, checkIconFn) {
     70  await SpecialPowers.pushPrefEnv({
     71    set: [
     72      // Turn on background updates
     73      ["extensions.update.enabled", true],
     74 
     75      // Point updates to the local mochitest server
     76      [
     77        "extensions.update.background.url",
     78        `${BASE}/browser_webext_update.json`,
     79      ],
     80    ],
     81  });
     82 
     83  Services.fog.testResetFOG();
     84 
     85  // Install version 1.0 of the test extension
     86  let addon = await promiseInstallAddon(url, {
     87    source: FAKE_INSTALL_TELEMETRY_SOURCE,
     88  });
     89  let addonId = addon.id;
     90 
     91  ok(addon, "Addon was installed");
     92  is(getBadgeStatus(), null, "Should not start out with an addon alert badge");
     93 
     94  // Trigger an update check and wait for the update for this addon
     95  // to be downloaded.
     96  let updatePromise = promiseInstallEvent(addon, "onDownloadEnded");
     97 
     98  AddonManagerPrivate.backgroundUpdateCheck();
     99  await updatePromise;
    100 
    101  is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
    102 
    103  // Find the menu entry for the update
    104  await gCUITestUtils.openMainMenu();
    105 
    106  let addons = PanelUI.addonNotificationContainer;
    107  is(addons.children.length, 1, "Have a menu entry for the update");
    108 
    109  // Click the menu item
    110  let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
    111  let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
    112  addons.children[0].click();
    113 
    114  // The click should hide the main menu. This is currently synchronous.
    115  Assert.notEqual(
    116    PanelUI.panel.state,
    117    "open",
    118    "Main menu is closed or closing."
    119  );
    120 
    121  // about:addons should load and go to the list of extensions
    122  let tab = await tabPromise;
    123  is(
    124    tab.linkedBrowser.currentURI.spec,
    125    "about:addons",
    126    "Browser is at about:addons"
    127  );
    128 
    129  const VIEW = "addons://list/extension";
    130  await promiseViewLoaded(tab, VIEW);
    131  let win = tab.linkedBrowser.contentWindow;
    132  ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
    133  is(
    134    win.gViewController.currentViewId,
    135    VIEW,
    136    "about:addons is at extensions list"
    137  );
    138 
    139  // Wait for the permission prompt, check the contents
    140  let panel = await popupPromise;
    141  checkIconFn(panel.getAttribute("icon"));
    142 
    143  // The original extension has 1 promptable permission and the new one
    144  // has 2 (history and <all_urls>) plus 1 non-promptable permission (cookies).
    145  // So we should only see the 1 new promptable permission in the notification.
    146  let permissionsListEl = document.getElementById(
    147    "addon-webext-perm-list-required"
    148  );
    149  ok(!permissionsListEl.hidden, "Permissions list is visible");
    150  ok(permissionsListEl.textContent, "Permissions list contains text");
    151  is(
    152    permissionsListEl.childElementCount,
    153    1,
    154    "Expect only 1 permission entry in the Permissions list"
    155  );
    156 
    157  // Cancel the update.
    158  panel.secondaryButton.click();
    159 
    160  addon = await AddonManager.getAddonByID(id);
    161  is(addon.version, "1.0", "Should still be running the old version");
    162 
    163  BrowserTestUtils.removeTab(tab);
    164 
    165  // Alert badge and hamburger menu items should be gone
    166  is(getBadgeStatus(), null, "Addon alert badge should be gone");
    167 
    168  await gCUITestUtils.openMainMenu();
    169  addons = PanelUI.addonNotificationContainer;
    170  is(addons.children.length, 0, "Update menu entries should be gone");
    171  await gCUITestUtils.hideMainMenu();
    172 
    173  // Re-check for an update
    174  updatePromise = promiseInstallEvent(addon, "onDownloadEnded");
    175  await AddonManagerPrivate.backgroundUpdateCheck();
    176  await updatePromise;
    177 
    178  is(getBadgeStatus(), "addon-alert", "Should have addon alert badge");
    179 
    180  // Find the menu entry for the update
    181  await gCUITestUtils.openMainMenu();
    182 
    183  addons = PanelUI.addonNotificationContainer;
    184  is(addons.children.length, 1, "Have a menu entry for the update");
    185 
    186  // Click the menu item
    187  tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons", true);
    188  popupPromise = promisePopupNotificationShown("addon-webext-permissions");
    189 
    190  addons.children[0].click();
    191 
    192  // Wait for about:addons to load
    193  tab = await tabPromise;
    194  is(tab.linkedBrowser.currentURI.spec, "about:addons");
    195 
    196  await promiseViewLoaded(tab, VIEW);
    197  win = tab.linkedBrowser.contentWindow;
    198  ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
    199  is(
    200    win.gViewController.currentViewId,
    201    VIEW,
    202    "about:addons is at extensions list"
    203  );
    204 
    205  // Wait for the permission prompt and accept it this time
    206  updatePromise = waitForUpdate(addon);
    207  panel = await popupPromise;
    208  panel.button.click();
    209 
    210  addon = await updatePromise;
    211  is(addon.version, "2.0", "Should have upgraded to the new version");
    212 
    213  BrowserTestUtils.removeTab(tab);
    214 
    215  is(getBadgeStatus(), null, "Addon alert badge should be gone");
    216 
    217  await addon.uninstall();
    218  await SpecialPowers.popPrefEnv();
    219 
    220  let gleanUpdates = AddonTestUtils.getAMGleanEvents("update");
    221 
    222  // Test that the expected telemetry events have been recorded (and that they include the
    223  // permission_prompt event).
    224  const amEvents = AddonTestUtils.getAMTelemetryEvents();
    225  const updateEvents = amEvents
    226    .filter(evt => evt.method === "update")
    227    .map(evt => {
    228      delete evt.value;
    229      return evt;
    230    });
    231 
    232  const expectedSteps = [
    233    // First update (cancelled).
    234    "started",
    235    "download_started",
    236    "download_completed",
    237    "permissions_prompt",
    238    "cancelled",
    239    // Second update (completed).
    240    "started",
    241    "download_started",
    242    "download_completed",
    243    "permissions_prompt",
    244    "completed",
    245  ];
    246 
    247  Assert.deepEqual(
    248    expectedSteps,
    249    updateEvents.map(evt => evt.extra && evt.extra.step),
    250    "Got the steps from the collected telemetry events"
    251  );
    252 
    253  Assert.deepEqual(
    254    expectedSteps,
    255    gleanUpdates.map(evt => evt.step),
    256    "Got the steps from the collected Glean events."
    257  );
    258 
    259  const method = "update";
    260  const object = "extension";
    261  const baseExtra = {
    262    addon_id: addonId,
    263    source: FAKE_INSTALL_TELEMETRY_SOURCE,
    264    step: "permissions_prompt",
    265    updated_from: "app",
    266  };
    267 
    268  // Expect the telemetry events to have num_strings set to 1, as only the origin permissions is going
    269  // to be listed in the permission prompt.
    270  Assert.deepEqual(
    271    updateEvents.filter(
    272      evt => evt.extra && evt.extra.step === "permissions_prompt"
    273    ),
    274    [
    275      { method, object, extra: { ...baseExtra, num_strings: "1" } },
    276      { method, object, extra: { ...baseExtra, num_strings: "1" } },
    277    ],
    278    "Got the expected permission_prompts events"
    279  );
    280 
    281  Assert.deepEqual(
    282    gleanUpdates.filter(e => e.step === "permissions_prompt"),
    283    [
    284      { ...baseExtra, addon_type: object, num_strings: "1" },
    285      { ...baseExtra, addon_type: object, num_strings: "1" },
    286    ],
    287    "Got the expected permission_prompt events from Glean."
    288  );
    289 }
    290 
    291 function checkDefaultIcon(icon) {
    292  is(
    293    icon,
    294    "chrome://mozapps/skin/extensions/extensionGeneric.svg",
    295    "Popup has the default extension icon"
    296  );
    297 }
    298 
    299 add_task(() =>
    300  backgroundUpdateTest(
    301    `${BASE}/browser_webext_update1.xpi`,
    302    ID,
    303    checkDefaultIcon
    304  )
    305 );
    306 function checkNonDefaultIcon(icon) {
    307  // The icon should come from the extension, don't bother with the precise
    308  // path, just make sure we've got a jar url pointing to the right path
    309  // inside the jar.
    310  ok(icon.startsWith("jar:file://"), "Icon is a jar url");
    311  ok(icon.endsWith("/icon.png"), "Icon is icon.png inside a jar");
    312 }
    313 
    314 add_task(() =>
    315  backgroundUpdateTest(
    316    `${BASE}/browser_webext_update_icon1.xpi`,
    317    ID_ICON,
    318    checkNonDefaultIcon
    319  )
    320 );