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 }