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 }