tor-browser

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

browser_contextmenu_sendpage.js (12843B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const fxaDevices = [
      7  {
      8    id: 1,
      9    name: "Foo",
     10    availableCommands: { "https://identity.mozilla.com/cmd/open-uri": "baz" },
     11    lastAccessTime: Date.now(),
     12  },
     13  {
     14    id: 2,
     15    name: "Bar",
     16    availableCommands: { "https://identity.mozilla.com/cmd/open-uri": "boo" },
     17    lastAccessTime: Date.now() + 60000, // add 30min
     18  },
     19  {
     20    id: 3,
     21    name: "Baz",
     22    clientRecord: "bar",
     23    lastAccessTime: Date.now() + 120000, // add 60min
     24  }, // Legacy send tab target (no availableCommands).
     25  { id: 4, name: "Homer" }, // Incompatible target.
     26 ];
     27 
     28 add_setup(async function () {
     29  await SpecialPowers.pushPrefEnv({
     30    set: [["test.wait300msAfterTabSwitch", true]],
     31  });
     32  await promiseSyncReady();
     33  await Services.search.init();
     34  // gSync.init() is called in a requestIdleCallback. Force its initialization.
     35  gSync.init();
     36  sinon
     37    .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId")
     38    .callsFake(fxaDeviceId => {
     39      let target = fxaDevices.find(c => c.id == fxaDeviceId);
     40      return target ? target.clientRecord : null;
     41    });
     42  sinon.stub(Weave.Service.clientsEngine, "getClientType").returns("desktop");
     43  await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
     44 });
     45 
     46 add_task(async function test_page_contextmenu() {
     47  const sandbox = setupSendTabMocks({ fxaDevices });
     48 
     49  await openContentContextMenu("#moztext", "context-sendpagetodevice");
     50  is(
     51    document.getElementById("context-sendpagetodevice").hidden,
     52    false,
     53    "Send page to device is shown"
     54  );
     55  is(
     56    document.getElementById("context-sendpagetodevice").disabled,
     57    false,
     58    "Send page to device is enabled"
     59  );
     60  checkPopup([
     61    { label: "Bar" },
     62    { label: "Foo" },
     63    "----",
     64    { label: "Send to All Devices" },
     65    { label: "Manage Devices..." },
     66  ]);
     67  await hideContentContextMenu();
     68 
     69  sandbox.restore();
     70 });
     71 
     72 add_task(async function test_link_contextmenu() {
     73  const sandbox = setupSendTabMocks({ fxaDevices });
     74  let expectation = sandbox
     75    .mock(gSync)
     76    .expects("sendTabToDevice")
     77    .once()
     78    .withExactArgs(
     79      "https://www.example.org/",
     80      [fxaDevices[1]],
     81      "Click on me!!"
     82    );
     83 
     84  // Add a link to the page
     85  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
     86    let a = content.document.createElement("a");
     87    a.href = "https://www.example.org";
     88    a.id = "testingLink";
     89    a.textContent = "Click on me!!";
     90    content.document.body.appendChild(a);
     91  });
     92 
     93  let contextMenu = await openContentContextMenu(
     94    "#testingLink",
     95    "context-sendlinktodevice",
     96    "context-sendlinktodevice-popup"
     97  );
     98 
     99  const expectOpenLinkInUserContextMenu =
    100    Services.prefs.getBoolPref("privacy.userContext.enabled") &&
    101    ContextualIdentityService.getPublicIdentities().length;
    102 
    103  const expectStripOnShareLink = Services.prefs.getBoolPref(
    104    "privacy.query_stripping.strip_on_share.enabled"
    105  );
    106 
    107  const expectTranslateSelection =
    108    Services.prefs.getBoolPref("browser.translations.enable") &&
    109    Services.prefs.getBoolPref("browser.translations.select.enable");
    110 
    111  const expectInspectAccessibility =
    112    Services.prefs.getBoolPref("devtools.accessibility.enabled", true) &&
    113    (Services.prefs.getBoolPref("devtools.everOpened", false) ||
    114      Services.prefs.getIntPref("devtools.selfxss.count", 0) > 0);
    115 
    116  const expectedArray = [
    117    "context-openlinkintab",
    118    ...(expectOpenLinkInUserContextMenu
    119      ? ["context-openlinkinusercontext-menu"]
    120      : []),
    121    "context-openlink",
    122    "context-openlinkprivate",
    123    "context-previewlink",
    124    "context-sep-open",
    125    "context-bookmarklink",
    126    "context-savelink",
    127    "context-copylink",
    128    ...(expectStripOnShareLink ? ["context-stripOnShareLink"] : []),
    129    "context-sep-copylink",
    130    "context-sendlinktodevice",
    131    "context-sep-sendlinktodevice",
    132    "context-searchselect",
    133    ...(expectTranslateSelection ? ["context-translate-selection"] : []),
    134    "context-ask-chat",
    135    "frame-sep",
    136    ...(expectInspectAccessibility ? ["context-inspect-a11y"] : []),
    137    "context-inspect",
    138  ];
    139 
    140  let menu = document.getElementById("contentAreaContextMenu");
    141 
    142  for (let i = 0, j = 0; i < menu.children.length; i++) {
    143    let item = menu.children[i];
    144    if (item.hidden) {
    145      continue;
    146    }
    147    Assert.equal(
    148      item.id,
    149      expectedArray[j],
    150      "Ids in context menu match expected values"
    151    );
    152    j++;
    153  }
    154 
    155  is(
    156    document.getElementById("context-sendlinktodevice").hidden,
    157    false,
    158    "Send link to device is shown"
    159  );
    160  is(
    161    document.getElementById("context-sendlinktodevice").disabled,
    162    false,
    163    "Send link to device is enabled"
    164  );
    165  contextMenu.activateItem(
    166    document
    167      .getElementById("context-sendlinktodevice-popup")
    168      .querySelector("menuitem")
    169  );
    170  await hideContentContextMenu();
    171 
    172  expectation.verify();
    173  sandbox.restore();
    174 });
    175 
    176 add_task(async function test_page_contextmenu_no_remote_clients() {
    177  const sandbox = setupSendTabMocks({ fxaDevices: [] });
    178 
    179  await openContentContextMenu("#moztext");
    180  is(
    181    document.getElementById("context-sendpagetodevice").hidden,
    182    true,
    183    "Send page to device is hidden"
    184  );
    185  is(
    186    document.getElementById("context-sendpagetodevice").disabled,
    187    false,
    188    "Send tab to device is enabled"
    189  );
    190  checkPopup();
    191  await hideContentContextMenu();
    192 
    193  sandbox.restore();
    194 });
    195 
    196 add_task(async function test_page_contextmenu_one_remote_client() {
    197  const sandbox = setupSendTabMocks({
    198    fxaDevices: [
    199      {
    200        id: 1,
    201        name: "Foo",
    202        availableCommands: {
    203          "https://identity.mozilla.com/cmd/open-uri": "baz",
    204        },
    205      },
    206    ],
    207  });
    208 
    209  await openContentContextMenu("#moztext", "context-sendpagetodevice");
    210  is(
    211    document.getElementById("context-sendpagetodevice").hidden,
    212    false,
    213    "Send page to device is shown"
    214  );
    215  is(
    216    document.getElementById("context-sendpagetodevice").disabled,
    217    false,
    218    "Send page to device is enabled"
    219  );
    220  checkPopup([{ label: "Foo" }]);
    221  await hideContentContextMenu();
    222 
    223  sandbox.restore();
    224 });
    225 
    226 add_task(async function test_page_contextmenu_not_sendable() {
    227  const sandbox = setupSendTabMocks({ fxaDevices, isSendableURI: false });
    228 
    229  await openContentContextMenu("#moztext");
    230  is(
    231    document.getElementById("context-sendpagetodevice").hidden,
    232    true,
    233    "Send page to device is hidden"
    234  );
    235  is(
    236    document.getElementById("context-sendpagetodevice").disabled,
    237    true,
    238    "Send page to device is disabled"
    239  );
    240  checkPopup();
    241  await hideContentContextMenu();
    242 
    243  sandbox.restore();
    244 });
    245 
    246 add_task(async function test_page_contextmenu_not_synced_yet() {
    247  const sandbox = setupSendTabMocks({ fxaDevices: null });
    248 
    249  await openContentContextMenu("#moztext");
    250  is(
    251    document.getElementById("context-sendpagetodevice").hidden,
    252    true,
    253    "Send page to device is hidden"
    254  );
    255  is(
    256    document.getElementById("context-sendpagetodevice").disabled,
    257    true,
    258    "Send page to device is disabled"
    259  );
    260  checkPopup();
    261  await hideContentContextMenu();
    262 
    263  sandbox.restore();
    264 });
    265 
    266 add_task(async function test_page_contextmenu_sync_not_ready_configured() {
    267  const sandbox = setupSendTabMocks({ syncReady: false });
    268 
    269  await openContentContextMenu("#moztext");
    270  is(
    271    document.getElementById("context-sendpagetodevice").hidden,
    272    true,
    273    "Send page to device is hidden"
    274  );
    275  is(
    276    document.getElementById("context-sendpagetodevice").disabled,
    277    true,
    278    "Send page to device is disabled"
    279  );
    280  checkPopup();
    281  await hideContentContextMenu();
    282 
    283  sandbox.restore();
    284 });
    285 
    286 add_task(async function test_page_contextmenu_sync_not_ready_other_state() {
    287  const sandbox = setupSendTabMocks({
    288    syncReady: false,
    289    state: UIState.STATUS_NOT_VERIFIED,
    290  });
    291 
    292  await openContentContextMenu("#moztext");
    293  is(
    294    document.getElementById("context-sendpagetodevice").hidden,
    295    true,
    296    "Send page to device is hidden"
    297  );
    298  is(
    299    document.getElementById("context-sendpagetodevice").disabled,
    300    false,
    301    "Send page to device is enabled"
    302  );
    303  checkPopup();
    304  await hideContentContextMenu();
    305 
    306  sandbox.restore();
    307 });
    308 
    309 add_task(async function test_page_contextmenu_unconfigured() {
    310  const sandbox = setupSendTabMocks({ state: UIState.STATUS_NOT_CONFIGURED });
    311 
    312  await openContentContextMenu("#moztext");
    313  is(
    314    document.getElementById("context-sendpagetodevice").hidden,
    315    true,
    316    "Send page to device is hidden"
    317  );
    318  is(
    319    document.getElementById("context-sendpagetodevice").disabled,
    320    false,
    321    "Send page to device is enabled"
    322  );
    323  checkPopup();
    324 
    325  await hideContentContextMenu();
    326 
    327  sandbox.restore();
    328 });
    329 
    330 add_task(async function test_page_contextmenu_not_verified() {
    331  const sandbox = setupSendTabMocks({ state: UIState.STATUS_NOT_VERIFIED });
    332 
    333  await openContentContextMenu("#moztext");
    334  is(
    335    document.getElementById("context-sendpagetodevice").hidden,
    336    true,
    337    "Send page to device is hidden"
    338  );
    339  is(
    340    document.getElementById("context-sendpagetodevice").disabled,
    341    false,
    342    "Send page to device is enabled"
    343  );
    344  checkPopup();
    345 
    346  await hideContentContextMenu();
    347 
    348  sandbox.restore();
    349 });
    350 
    351 add_task(async function test_page_contextmenu_login_failed() {
    352  const sandbox = setupSendTabMocks({ state: UIState.STATUS_LOGIN_FAILED });
    353 
    354  await openContentContextMenu("#moztext");
    355  is(
    356    document.getElementById("context-sendpagetodevice").hidden,
    357    true,
    358    "Send page to device is hidden"
    359  );
    360  is(
    361    document.getElementById("context-sendpagetodevice").disabled,
    362    false,
    363    "Send page to device is enabled"
    364  );
    365  checkPopup();
    366 
    367  await hideContentContextMenu();
    368 
    369  sandbox.restore();
    370 });
    371 
    372 add_task(async function test_page_contextmenu_fxa_disabled() {
    373  const getter = sinon.stub(gSync, "FXA_ENABLED").get(() => false);
    374  gSync.onFxaDisabled(); // Would have been called on gSync initialization if FXA_ENABLED had been set.
    375  await openContentContextMenu("#moztext");
    376  is(
    377    document.getElementById("context-sendpagetodevice").hidden,
    378    true,
    379    "Send page to device is hidden"
    380  );
    381  await hideContentContextMenu();
    382  getter.restore();
    383  [...document.querySelectorAll(".sync-ui-item")].forEach(
    384    e => (e.hidden = false)
    385  );
    386 });
    387 
    388 // We are not going to bother testing the visibility of context-sendlinktodevice
    389 // since it uses the exact same code.
    390 // However, browser_contextmenu.js contains tests that verify its presence.
    391 
    392 add_task(async function teardown() {
    393  Weave.Service.clientsEngine.getClientByFxaDeviceId.restore();
    394  Weave.Service.clientsEngine.getClientType.restore();
    395  gBrowser.removeCurrentTab();
    396 });
    397 
    398 function checkPopup(expectedItems = null) {
    399  const popup = document.getElementById("context-sendpagetodevice-popup");
    400  if (!expectedItems) {
    401    is(popup.state, "closed", "Popup should be hidden.");
    402    return;
    403  }
    404  const menuItems = popup.children;
    405  for (let i = 0; i < menuItems.length; i++) {
    406    const menuItem = menuItems[i];
    407    const expectedItem = expectedItems[i];
    408    if (expectedItem === "----") {
    409      is(menuItem.nodeName, "menuseparator", "Found a separator");
    410      continue;
    411    }
    412    is(menuItem.nodeName, "menuitem", "Found a menu item");
    413    // Bug workaround, menuItem.label "…" encoding is different than ours.
    414    is(
    415      menuItem.label.normalize("NFKC"),
    416      expectedItem.label,
    417      "Correct menu item label"
    418    );
    419    is(
    420      menuItem.disabled,
    421      !!expectedItem.disabled,
    422      "Correct menu item disabled state"
    423    );
    424  }
    425  // check the length last - the above loop might have given us other clues...
    426  is(
    427    menuItems.length,
    428    expectedItems.length,
    429    "Popup has the expected children count."
    430  );
    431 }
    432 
    433 async function openContentContextMenu(selector, openSubmenuId = null) {
    434  const contextMenu = document.getElementById("contentAreaContextMenu");
    435  is(contextMenu.state, "closed", "checking if popup is closed");
    436 
    437  const awaitPopupShown = BrowserTestUtils.waitForEvent(
    438    contextMenu,
    439    "popupshown"
    440  );
    441  await BrowserTestUtils.synthesizeMouse(
    442    selector,
    443    0,
    444    0,
    445    {
    446      type: "contextmenu",
    447      button: 2,
    448      shiftkey: false,
    449      centered: true,
    450    },
    451    gBrowser.selectedBrowser
    452  );
    453  await awaitPopupShown;
    454 
    455  if (openSubmenuId) {
    456    const menu = document.getElementById(openSubmenuId);
    457    const menuPopup = menu.menupopup;
    458    const menuPopupPromise = BrowserTestUtils.waitForEvent(
    459      menuPopup,
    460      "popupshown"
    461    );
    462    menu.openMenu(true);
    463    await menuPopupPromise;
    464  }
    465  return contextMenu;
    466 }
    467 
    468 async function hideContentContextMenu() {
    469  const contextMenu = document.getElementById("contentAreaContextMenu");
    470  const awaitPopupHidden = BrowserTestUtils.waitForEvent(
    471    contextMenu,
    472    "popuphidden"
    473  );
    474  contextMenu.hidePopup();
    475  await awaitPopupHidden;
    476 }