tor-browser

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

browser_copy_link_to_highlight.js (15743B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 let listService;
      7 
      8 add_setup(async function () {
      9  await SpecialPowers.pushPrefEnv({
     10    set: [
     11      ["privacy.query_stripping.strip_list", "stripParam"],
     12      ["privacy.query_stripping.strip_on_share.enabled", true],
     13    ],
     14  });
     15 
     16  // Get the list service so we can wait for it to be fully initialized before running tests.
     17  listService = Cc["@mozilla.org/query-stripping-list-service;1"].getService(
     18    Ci.nsIURLQueryStrippingListService
     19  );
     20 
     21  await listService.testWaitForInit();
     22 });
     23 
     24 /*
     25  Tests for the "copy link to highlight" options in the browser content context menu
     26 */
     27 
     28 // Menu items should not be visible if no text is selected
     29 add_task(async function notVisibleIfNoSelection() {
     30  await testCopyLinkToHighlight({
     31    testPage: loremIpsumTestPage(false),
     32    runTests: async ({ copyLinkToHighlight, copyCleanLinkToHighlight }) => {
     33      Assert.ok(
     34        !BrowserTestUtils.isVisible(copyLinkToHighlight),
     35        "Copy Link to Highlight Menu item is not visible"
     36      );
     37      Assert.ok(
     38        !BrowserTestUtils.isVisible(copyCleanLinkToHighlight),
     39        "Copy Clean Link to Highlight Menu item is not visible"
     40      );
     41    },
     42  });
     43 });
     44 
     45 // Menu items should not be visible if selection is in a contenteditable.
     46 add_task(async function notVisibleInEditable() {
     47  await testCopyLinkToHighlight({
     48    testPage: editableTestPage(),
     49    runTests: async ({ copyLinkToHighlight, copyCleanLinkToHighlight }) => {
     50      Assert.ok(
     51        !BrowserTestUtils.isVisible(copyLinkToHighlight),
     52        "Copy Link to Highlight Menu item is not visible"
     53      );
     54      Assert.ok(
     55        !BrowserTestUtils.isVisible(copyCleanLinkToHighlight),
     56        "Copy Clean Link to Highlight Menu item is not visible"
     57      );
     58    },
     59  });
     60 });
     61 
     62 // Menu items should be visible and not disabled if text is selected
     63 add_task(async function isVisibleIfSelection() {
     64  await testCopyLinkToHighlight({
     65    testPage: loremIpsumTestPage(true),
     66    runTests: async ({ copyLinkToHighlight, copyCleanLinkToHighlight }) => {
     67      // tests for visibility
     68      Assert.ok(
     69        BrowserTestUtils.isVisible(copyLinkToHighlight),
     70        "Copy Link to Highlight Menu item is visible"
     71      );
     72      Assert.ok(
     73        BrowserTestUtils.isVisible(copyCleanLinkToHighlight),
     74        "Copy Clean Link to Highlight Menu item is visible"
     75      );
     76 
     77      // tests for enabled menu items
     78      Assert.ok(
     79        !copyLinkToHighlight.disabled,
     80        "Copy Link to Highlight Menu item is enabled"
     81      );
     82    },
     83  });
     84 });
     85 
     86 // Clicking "Copy Link to Highlight" copies the URL with text fragment to the clipboard
     87 add_task(async function copiesToClipboard() {
     88  await testCopyLinkToHighlight({
     89    testPage: loremIpsumTestPage(true),
     90    runTests: async ({ copyLinkToHighlight }) => {
     91      await SimpleTest.promiseClipboardChange(
     92        "https://www.example.com/?stripParam=1234#:~:text=eiusmod%20tempor%20incididunt&text=labore",
     93        async () => {
     94          await BrowserTestUtils.waitForCondition(
     95            () => !copyLinkToHighlight.disabled,
     96            "Waiting for copyLinkToHighlight to become enabled"
     97          );
     98          copyLinkToHighlight
     99            .closest("menupopup")
    100            .activateItem(copyLinkToHighlight);
    101        }
    102      );
    103    },
    104  });
    105 });
    106 
    107 // Clicking "Copy Clean Link to Highlight" copies the URL with text fragment and without tracking query params to the clipboard
    108 add_task(async function copiesCleanLinkToClipboard() {
    109  await testCopyLinkToHighlight({
    110    testPage: loremIpsumTestPage(true),
    111    runTests: async ({ copyCleanLinkToHighlight }) => {
    112      await SimpleTest.promiseClipboardChange(
    113        "https://www.example.com/#:~:text=eiusmod%20tempor%20incididunt&text=labore",
    114        async () => {
    115          await BrowserTestUtils.waitForCondition(
    116            () => !copyCleanLinkToHighlight.disabled,
    117            "Waiting for copyLinkToHighlight to become enabled"
    118          );
    119          copyCleanLinkToHighlight
    120            .closest("menupopup")
    121            .activateItem(copyCleanLinkToHighlight);
    122        }
    123      );
    124    },
    125  });
    126 });
    127 
    128 // If there is already a highlight on the page, "Copy Link to Highlight" should work even if no text is selected.
    129 add_task(async function copiesToClipWithExistingHighlightAndNoSelection() {
    130  await testCopyLinkToHighlight({
    131    testPage: loremIpsumTestPage(false, true),
    132    runTests: async ({ copyLinkToHighlight }) => {
    133      await SimpleTest.promiseClipboardChange(
    134        "https://www.example.com/?stripParam=1234#:~:text=Lorem",
    135        async () => {
    136          await BrowserTestUtils.waitForCondition(
    137            () =>
    138              !copyLinkToHighlight.hasAttribute("disabled") ||
    139              copyLinkToHighlight.getAttribute("disabled") === "false",
    140            "Waiting for copyLinkToHighlight to become enabled"
    141          );
    142          copyLinkToHighlight
    143            .closest("menupopup")
    144            .activateItem(copyLinkToHighlight);
    145        }
    146      );
    147    },
    148  });
    149 });
    150 
    151 // If there is already a highlight on the page and text is selected,
    152 // "Copy Link to Highlight" should use the selection over the existing highlight.
    153 add_task(async function copiesToClipWithExistingHighlightAndSelection() {
    154  await testCopyLinkToHighlight({
    155    testPage: loremIpsumTestPage(true, true),
    156    runTests: async ({ copyLinkToHighlight }) => {
    157      await SimpleTest.promiseClipboardChange(
    158        "https://www.example.com/?stripParam=1234#:~:text=eiusmod%20tempor%20incididunt&text=labore",
    159        async () => {
    160          await BrowserTestUtils.waitForCondition(
    161            () =>
    162              !copyLinkToHighlight.hasAttribute("disabled") ||
    163              copyLinkToHighlight.getAttribute("disabled") === "false",
    164            "Waiting for copyLinkToHighlight to become enabled"
    165          );
    166          copyLinkToHighlight
    167            .closest("menupopup")
    168            .activateItem(copyLinkToHighlight);
    169        }
    170      );
    171    },
    172  });
    173 });
    174 /**
    175 * Tests the "Remove Highlight" context menu item.
    176 *
    177 * This test checks that the menu item is present and enabled if there is a
    178 * text fragment in the URL.
    179 * It also checks that after removing the highlights the URL in the URL bar
    180 * does not contain the text fragment anymore. In this test, there is no fragment
    181 * in the URL, so the URL must not have a hash (not even an empty one), because
    182 * this would trigger a hashchange event and the page would scroll to the top.
    183 */
    184 add_task(async function removesAllHighlightsWithEmptyFragment() {
    185  await BrowserTestUtils.withNewTab(
    186    "https://www.example.com/",
    187    async function (browser) {
    188      await loremIpsumTestPage(false)(browser);
    189      await SpecialPowers.spawn(browser, [], async function () {
    190        content.location.hash = ":~:text=lorem";
    191      });
    192 
    193      is(
    194        gURLBar.value,
    195        "www.example.com/#:~:text=lorem",
    196        "URL bar does contain a hash after adding a text fragment"
    197      );
    198      let contextMenu = document.getElementById("contentAreaContextMenu");
    199 
    200      let awaitPopupShown = BrowserTestUtils.waitForEvent(
    201        contextMenu,
    202        "popupshown"
    203      );
    204      await BrowserTestUtils.synthesizeMouseAtCenter(
    205        "#text",
    206        { type: "contextmenu", button: 2 },
    207        browser
    208      );
    209      await awaitPopupShown;
    210      let awaitPopupHidden = BrowserTestUtils.waitForEvent(
    211        contextMenu,
    212        "popuphidden"
    213      );
    214 
    215      let removeAllHighlights = contextMenu.querySelector(
    216        "#context-remove-highlight"
    217      );
    218      ok(removeAllHighlights, '"Remove Highlight" menu item is present');
    219      ok(
    220        BrowserTestUtils.isVisible(removeAllHighlights),
    221        '"Remove Highlight" menu item is visible'
    222      );
    223      let awaitLocationChange = BrowserTestUtils.waitForLocationChange(
    224        gBrowser,
    225        "https://www.example.com/"
    226      );
    227      removeAllHighlights
    228        .closest("menupopup")
    229        .activateItem(removeAllHighlights);
    230 
    231      await awaitPopupHidden;
    232      await awaitLocationChange;
    233 
    234      is(
    235        gURLBar.value,
    236        "www.example.com",
    237        "The URL does not contain a text fragment anymore, and also no fragment (not even an empty one)"
    238      );
    239    }
    240  );
    241 });
    242 
    243 /**
    244 * Tests the "Remove Highlight" context menu item for a page which has both
    245 * a fragment and a text fragment in the URL. After removing the highlights,
    246 * the text fragment should be removed from the URL, but the fragment must still
    247 * be there.
    248 */
    249 add_task(async function removesAllHighlightsWithNonEmptyFragment() {
    250  await BrowserTestUtils.withNewTab(
    251    "https://www.example.com/",
    252    async function (browser) {
    253      await loremIpsumTestPage(false)(browser);
    254      await SpecialPowers.spawn(browser, [], async function () {
    255        content.location.hash = "foo:~:text=lorem";
    256      });
    257 
    258      is(
    259        gURLBar.value,
    260        "www.example.com/#foo:~:text=lorem",
    261        "URL bar does contain a fragment and a text fragment"
    262      );
    263      let contextMenu = document.getElementById("contentAreaContextMenu");
    264      let awaitPopupShown = BrowserTestUtils.waitForEvent(
    265        contextMenu,
    266        "popupshown"
    267      );
    268      await BrowserTestUtils.synthesizeMouseAtCenter(
    269        "#text",
    270        { type: "contextmenu", button: 2 },
    271        browser
    272      );
    273      await awaitPopupShown;
    274      let awaitPopupHidden = BrowserTestUtils.waitForEvent(
    275        contextMenu,
    276        "popuphidden"
    277      );
    278 
    279      let removeAllHighlights = contextMenu.querySelector(
    280        "#context-remove-highlight"
    281      );
    282      ok(removeAllHighlights, '"Remove Highlight" menu item is present');
    283      ok(
    284        BrowserTestUtils.isVisible(removeAllHighlights),
    285        '"Remove Highlight" menu item is visible'
    286      );
    287      let awaitLocationChange = BrowserTestUtils.waitForLocationChange(
    288        gBrowser,
    289        "https://www.example.com/#foo"
    290      );
    291      removeAllHighlights
    292        .closest("menupopup")
    293        .activateItem(removeAllHighlights);
    294 
    295      await awaitPopupHidden;
    296      await awaitLocationChange;
    297 
    298      is(
    299        gURLBar.value,
    300        "www.example.com/#foo",
    301        "Text Fragment is removed from the URL, fragment is still there"
    302      );
    303    }
    304  );
    305 });
    306 
    307 /* Bug 2004502: When strip_on_share is disabled, "Copy Link to Highlight"
    308 * should still be visible but "Copy Clean Link to Highlight" should be hidden.
    309 */
    310 add_task(async function copyLinkVisibleWhenStripOnShareDisabled() {
    311  await SpecialPowers.pushPrefEnv({
    312    set: [["privacy.query_stripping.strip_on_share.enabled", false]],
    313  });
    314 
    315  await testCopyLinkToHighlight({
    316    testPage: loremIpsumTestPage(true),
    317    runTests: async ({ copyLinkToHighlight, copyCleanLinkToHighlight }) => {
    318      Assert.ok(
    319        BrowserTestUtils.isVisible(copyLinkToHighlight),
    320        "Copy Link to Highlight Menu item is visible when strip_on_share is disabled"
    321      );
    322      Assert.ok(
    323        !BrowserTestUtils.isVisible(copyCleanLinkToHighlight),
    324        "Copy Clean Link to Highlight Menu item is not visible when strip_on_share is disabled"
    325      );
    326    },
    327  });
    328 
    329  await SpecialPowers.popPrefEnv();
    330 });
    331 
    332 /**
    333 * Creates a document which contains a contenteditable element with some content.
    334 * Additionally selects the editable content.
    335 *
    336 * @returns Returns an async function which creates the content.
    337 */
    338 function editableTestPage() {
    339  return async function (browser) {
    340    await SpecialPowers.spawn(browser, [], async function () {
    341      const editable = content.document.createElement("div");
    342      editable.contentEditable = true;
    343      editable.textContent = "This is editable";
    344      const range = content.document.createRange();
    345      range.selectNodeContents(editable);
    346      content.getSelection().addRange(range);
    347    });
    348  };
    349 }
    350 
    351 /**
    352 * Provides an async function that creates a document with some text nodes,
    353 * and (if `isTextSelected == true`) also creates some selection ranges.
    354 *
    355 * @param {boolean} isTextSelected If true, two ranges are created in the
    356 *                                 document and added to the selection.
    357 * @param {boolean} loadWithExistingHighlight If true, the page is loaded
    358 *                                           with a text fragment in the URL.
    359 * @returns Async function which creates the content.
    360 */
    361 function loremIpsumTestPage(isTextSelected, loadWithExistingHighlight = false) {
    362  return async function (browser) {
    363    await SpecialPowers.spawn(
    364      browser,
    365      [isTextSelected, loadWithExistingHighlight],
    366      async function (selectText, existingHighlight) {
    367        const textBegin = content.document.createTextNode(
    368          "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
    369        );
    370        const textMiddle = content.document.createTextNode(
    371          "eiusmod tempor incididunt"
    372        );
    373        const textEnd = content.document.createTextNode(
    374          " ut labore et dolore magna aliqua. Est nulla nostrud velit dolore aliquip ipsum do sint cillum excepteur adipisicing ipsum irure. Sit sunt reprehenderit laboris labore magna exercitation amet fugiat nisi ad laborum veniam nisi. Est ex proident anim eiusmod veniam ipsum officia in ipsum deserunt voluptate. Enim anim cillum elit tempor consequat esse exercitation."
    375        );
    376 
    377        const paragraph = content.document.createElement("p");
    378        const span = content.document.createElement("span");
    379        span.appendChild(textMiddle);
    380        span.id = "span";
    381 
    382        paragraph.appendChild(textBegin);
    383        paragraph.appendChild(span);
    384        paragraph.appendChild(textEnd);
    385 
    386        paragraph.id = "text";
    387        content.document.body.prepend(paragraph);
    388 
    389        if (existingHighlight) {
    390          content.location.hash = ":~:text=Lorem";
    391        }
    392 
    393        if (selectText) {
    394          const selection = content.getSelection();
    395          const range = content.document.createRange();
    396          range.selectNodeContents(span);
    397          selection.addRange(range);
    398          const range2 = content.document.createRange();
    399          range2.setStart(textEnd, 4);
    400          range2.setEnd(textEnd, 10);
    401          selection.addRange(range2);
    402        }
    403      }
    404    );
    405  };
    406 }
    407 
    408 /**
    409 * Opens a new tab with `testPage` as content,
    410 * opens the context menu and checks if "Copy Link to Highlight" items are
    411 * visible, enabled, and functioning as expected.
    412 *
    413 * @param {Function} testPage - Content of the test page to load.
    414 * @param {Function} runTests - Async callback function for running assertions,
    415 * receives references to both menu items
    416 */
    417 async function testCopyLinkToHighlight({ testPage, runTests }) {
    418  await BrowserTestUtils.withNewTab(
    419    "www.example.com?stripParam=1234",
    420    async function (browser) {
    421      // Add some text to the page, optionally select some of it
    422      await testPage(browser);
    423 
    424      let contextMenu = document.getElementById("contentAreaContextMenu");
    425      // Open the context menu
    426      let awaitPopupShown = BrowserTestUtils.waitForEvent(
    427        contextMenu,
    428        "popupshown"
    429      );
    430      await BrowserTestUtils.synthesizeMouseAtCenter(
    431        "#span",
    432        { type: "contextmenu", button: 2 },
    433        browser
    434      );
    435      await awaitPopupShown;
    436      let awaitPopupHidden = BrowserTestUtils.waitForEvent(
    437        contextMenu,
    438        "popuphidden"
    439      );
    440 
    441      // Run some tests with each menu item
    442      let copyLinkToHighlight = contextMenu.querySelector(
    443        "#context-copy-link-to-highlight"
    444      );
    445      let copyCleanLinkToHighlight = contextMenu.querySelector(
    446        "#context-copy-clean-link-to-highlight"
    447      );
    448 
    449      await runTests({ copyLinkToHighlight, copyCleanLinkToHighlight });
    450 
    451      contextMenu.hidePopup();
    452      await awaitPopupHidden;
    453    }
    454  );
    455 }