forked from mirrors/gecko-dev
		
	 a14c746228
			
		
	
	
		a14c746228
		
	
	
	
	
		
			
			Bug 1805414 will move menu event handling to the DOM. With that change the current synthetic click behavior of XUL menuitems breaks. On current central, we rely on nsMenuFrame::HandleEvent not getting called at all for synthetic clicks, and instead we just fire a command event synchronously here: https://searchfox.org/mozilla-central/rev/a0d4f8f112c5c792ae272bf6ce50763ddd23ffa2/dom/xul/nsXULElement.cpp#1071 After my patch the command event is fired properly (potentially asynchronously too) by the regular menu activation machinery, which is preferable. * They fire a command event synchronously (even though on some platforms like macOS activating a context menu item is async). * They use a totally different codepath from what a user does. * They don't deal with native menus, etc. We have a proper API for this (activateItem) which takes a much more closer codepath to what users do, requires that the menu is shown, etc. Use that API instead for testing. As a benefit, tests now do not need to close the context menu manually when clicking on a menu item (because we trigger the same code path as users clicking the menu). Differential Revision: https://phabricator.services.mozilla.com/D164567
		
			
				
	
	
		
			465 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			465 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* Any copyright is dedicated to the Public Domain.
 | |
|    http://creativecommons.org/publicdomain/zero/1.0/ */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const fxaDevices = [
 | |
|   {
 | |
|     id: 1,
 | |
|     name: "Foo",
 | |
|     availableCommands: { "https://identity.mozilla.com/cmd/open-uri": "baz" },
 | |
|     lastAccessTime: Date.now(),
 | |
|   },
 | |
|   {
 | |
|     id: 2,
 | |
|     name: "Bar",
 | |
|     availableCommands: { "https://identity.mozilla.com/cmd/open-uri": "boo" },
 | |
|     lastAccessTime: Date.now() + 60000, // add 30min
 | |
|   },
 | |
|   {
 | |
|     id: 3,
 | |
|     name: "Baz",
 | |
|     clientRecord: "bar",
 | |
|     lastAccessTime: Date.now() + 120000, // add 60min
 | |
|   }, // Legacy send tab target (no availableCommands).
 | |
|   { id: 4, name: "Homer" }, // Incompatible target.
 | |
| ];
 | |
| 
 | |
| add_setup(async function() {
 | |
|   await promiseSyncReady();
 | |
|   await Services.search.init();
 | |
|   // gSync.init() is called in a requestIdleCallback. Force its initialization.
 | |
|   gSync.init();
 | |
|   sinon
 | |
|     .stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId")
 | |
|     .callsFake(fxaDeviceId => {
 | |
|       let target = fxaDevices.find(c => c.id == fxaDeviceId);
 | |
|       return target ? target.clientRecord : null;
 | |
|     });
 | |
|   sinon.stub(Weave.Service.clientsEngine, "getClientType").returns("desktop");
 | |
|   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu() {
 | |
|   const sandbox = setupSendTabMocks({ fxaDevices });
 | |
| 
 | |
|   await openContentContextMenu("#moztext", "context-sendpagetodevice");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     false,
 | |
|     "Send page to device is shown"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     false,
 | |
|     "Send page to device is enabled"
 | |
|   );
 | |
|   checkPopup([
 | |
|     { label: "Bar" },
 | |
|     { label: "Foo" },
 | |
|     "----",
 | |
|     { label: "Send to All Devices" },
 | |
|     { label: "Manage Devices..." },
 | |
|   ]);
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_link_contextmenu() {
 | |
|   const sandbox = setupSendTabMocks({ fxaDevices });
 | |
|   let expectation = sandbox
 | |
|     .mock(gSync)
 | |
|     .expects("sendTabToDevice")
 | |
|     .once()
 | |
|     .withExactArgs(
 | |
|       "https://www.example.org/",
 | |
|       [fxaDevices[1]],
 | |
|       "Click on me!!"
 | |
|     );
 | |
| 
 | |
|   // Add a link to the page
 | |
|   await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
 | |
|     let a = content.document.createElement("a");
 | |
|     a.href = "https://www.example.org";
 | |
|     a.id = "testingLink";
 | |
|     a.textContent = "Click on me!!";
 | |
|     content.document.body.appendChild(a);
 | |
|   });
 | |
| 
 | |
|   let contextMenu = await openContentContextMenu(
 | |
|     "#testingLink",
 | |
|     "context-sendlinktodevice",
 | |
|     "context-sendlinktodevice-popup"
 | |
|   );
 | |
| 
 | |
|   let expectedArray = ["context-openlinkintab"];
 | |
| 
 | |
|   if (
 | |
|     Services.prefs.getBoolPref("privacy.userContext.enabled") &&
 | |
|     ContextualIdentityService.getPublicIdentities().length
 | |
|   ) {
 | |
|     expectedArray.push("context-openlinkinusercontext-menu");
 | |
|   }
 | |
| 
 | |
|   expectedArray.push(
 | |
|     "context-openlink",
 | |
|     "context-openlinkprivate",
 | |
|     "context-sep-open",
 | |
|     "context-bookmarklink",
 | |
|     "context-savelink",
 | |
|     "context-savelinktopocket",
 | |
|     "context-copylink",
 | |
|     "context-sendlinktodevice",
 | |
|     "context-sep-sendlinktodevice",
 | |
|     "context-searchselect",
 | |
|     "frame-sep"
 | |
|   );
 | |
| 
 | |
|   if (
 | |
|     Services.prefs.getBoolPref("devtools.accessibility.enabled", true) &&
 | |
|     (Services.prefs.getBoolPref("devtools.everOpened", false) ||
 | |
|       Services.prefs.getIntPref("devtools.selfxss.count", 0) > 0)
 | |
|   ) {
 | |
|     expectedArray.push("context-inspect-a11y");
 | |
|   }
 | |
| 
 | |
|   expectedArray.push("context-inspect");
 | |
| 
 | |
|   let menu = document.getElementById("contentAreaContextMenu");
 | |
| 
 | |
|   for (let i = 0, j = 0; i < menu.children.length; i++) {
 | |
|     let item = menu.children[i];
 | |
|     if (item.hidden) {
 | |
|       continue;
 | |
|     }
 | |
|     Assert.equal(
 | |
|       item.id,
 | |
|       expectedArray[j],
 | |
|       "Ids in context menu match expected values"
 | |
|     );
 | |
|     j++;
 | |
|   }
 | |
| 
 | |
|   is(
 | |
|     document.getElementById("context-sendlinktodevice").hidden,
 | |
|     false,
 | |
|     "Send link to device is shown"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendlinktodevice").disabled,
 | |
|     false,
 | |
|     "Send link to device is enabled"
 | |
|   );
 | |
|   contextMenu.activateItem(
 | |
|     document
 | |
|       .getElementById("context-sendlinktodevice-popup")
 | |
|       .querySelector("menuitem")
 | |
|   );
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   expectation.verify();
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_no_remote_clients() {
 | |
|   const sandbox = setupSendTabMocks({ fxaDevices: [] });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     false,
 | |
|     "Send tab to device is enabled"
 | |
|   );
 | |
|   checkPopup();
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_one_remote_client() {
 | |
|   const sandbox = setupSendTabMocks({
 | |
|     fxaDevices: [
 | |
|       {
 | |
|         id: 1,
 | |
|         name: "Foo",
 | |
|         availableCommands: {
 | |
|           "https://identity.mozilla.com/cmd/open-uri": "baz",
 | |
|         },
 | |
|       },
 | |
|     ],
 | |
|   });
 | |
| 
 | |
|   await openContentContextMenu("#moztext", "context-sendpagetodevice");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     false,
 | |
|     "Send page to device is shown"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     false,
 | |
|     "Send page to device is enabled"
 | |
|   );
 | |
|   checkPopup([{ label: "Foo" }]);
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_not_sendable() {
 | |
|   const sandbox = setupSendTabMocks({ fxaDevices, isSendableURI: false });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     true,
 | |
|     "Send page to device is disabled"
 | |
|   );
 | |
|   checkPopup();
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_not_synced_yet() {
 | |
|   const sandbox = setupSendTabMocks({ fxaDevices: null });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     true,
 | |
|     "Send page to device is disabled"
 | |
|   );
 | |
|   checkPopup();
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_sync_not_ready_configured() {
 | |
|   const sandbox = setupSendTabMocks({ syncReady: false });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     true,
 | |
|     "Send page to device is disabled"
 | |
|   );
 | |
|   checkPopup();
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_sync_not_ready_other_state() {
 | |
|   const sandbox = setupSendTabMocks({
 | |
|     syncReady: false,
 | |
|     state: UIState.STATUS_NOT_VERIFIED,
 | |
|   });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     false,
 | |
|     "Send page to device is enabled"
 | |
|   );
 | |
|   checkPopup();
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_unconfigured() {
 | |
|   const sandbox = setupSendTabMocks({ state: UIState.STATUS_NOT_CONFIGURED });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     false,
 | |
|     "Send page to device is enabled"
 | |
|   );
 | |
|   checkPopup();
 | |
| 
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_not_verified() {
 | |
|   const sandbox = setupSendTabMocks({ state: UIState.STATUS_NOT_VERIFIED });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     false,
 | |
|     "Send page to device is enabled"
 | |
|   );
 | |
|   checkPopup();
 | |
| 
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_login_failed() {
 | |
|   const sandbox = setupSendTabMocks({ state: UIState.STATUS_LOGIN_FAILED });
 | |
| 
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").disabled,
 | |
|     false,
 | |
|     "Send page to device is enabled"
 | |
|   );
 | |
|   checkPopup();
 | |
| 
 | |
|   await hideContentContextMenu();
 | |
| 
 | |
|   sandbox.restore();
 | |
| });
 | |
| 
 | |
| add_task(async function test_page_contextmenu_fxa_disabled() {
 | |
|   const getter = sinon.stub(gSync, "FXA_ENABLED").get(() => false);
 | |
|   gSync.onFxaDisabled(); // Would have been called on gSync initialization if FXA_ENABLED had been set.
 | |
|   await openContentContextMenu("#moztext");
 | |
|   is(
 | |
|     document.getElementById("context-sendpagetodevice").hidden,
 | |
|     true,
 | |
|     "Send page to device is hidden"
 | |
|   );
 | |
|   await hideContentContextMenu();
 | |
|   getter.restore();
 | |
|   [...document.querySelectorAll(".sync-ui-item")].forEach(
 | |
|     e => (e.hidden = false)
 | |
|   );
 | |
| });
 | |
| 
 | |
| // We are not going to bother testing the visibility of context-sendlinktodevice
 | |
| // since it uses the exact same code.
 | |
| // However, browser_contextmenu.js contains tests that verify its presence.
 | |
| 
 | |
| add_task(async function teardown() {
 | |
|   Weave.Service.clientsEngine.getClientByFxaDeviceId.restore();
 | |
|   Weave.Service.clientsEngine.getClientType.restore();
 | |
|   gBrowser.removeCurrentTab();
 | |
| });
 | |
| 
 | |
| function checkPopup(expectedItems = null) {
 | |
|   const popup = document.getElementById("context-sendpagetodevice-popup");
 | |
|   if (!expectedItems) {
 | |
|     is(popup.state, "closed", "Popup should be hidden.");
 | |
|     return;
 | |
|   }
 | |
|   const menuItems = popup.children;
 | |
|   for (let i = 0; i < menuItems.length; i++) {
 | |
|     const menuItem = menuItems[i];
 | |
|     const expectedItem = expectedItems[i];
 | |
|     if (expectedItem === "----") {
 | |
|       is(menuItem.nodeName, "menuseparator", "Found a separator");
 | |
|       continue;
 | |
|     }
 | |
|     is(menuItem.nodeName, "menuitem", "Found a menu item");
 | |
|     // Bug workaround, menuItem.label "…" encoding is different than ours.
 | |
|     is(
 | |
|       menuItem.label.normalize("NFKC"),
 | |
|       expectedItem.label,
 | |
|       "Correct menu item label"
 | |
|     );
 | |
|     is(
 | |
|       menuItem.disabled,
 | |
|       !!expectedItem.disabled,
 | |
|       "Correct menu item disabled state"
 | |
|     );
 | |
|   }
 | |
|   // check the length last - the above loop might have given us other clues...
 | |
|   is(
 | |
|     menuItems.length,
 | |
|     expectedItems.length,
 | |
|     "Popup has the expected children count."
 | |
|   );
 | |
| }
 | |
| 
 | |
| async function openContentContextMenu(selector, openSubmenuId = null) {
 | |
|   const contextMenu = document.getElementById("contentAreaContextMenu");
 | |
|   is(contextMenu.state, "closed", "checking if popup is closed");
 | |
| 
 | |
|   const awaitPopupShown = BrowserTestUtils.waitForEvent(
 | |
|     contextMenu,
 | |
|     "popupshown"
 | |
|   );
 | |
|   await BrowserTestUtils.synthesizeMouse(
 | |
|     selector,
 | |
|     0,
 | |
|     0,
 | |
|     {
 | |
|       type: "contextmenu",
 | |
|       button: 2,
 | |
|       shiftkey: false,
 | |
|       centered: true,
 | |
|     },
 | |
|     gBrowser.selectedBrowser
 | |
|   );
 | |
|   await awaitPopupShown;
 | |
| 
 | |
|   if (openSubmenuId) {
 | |
|     const menu = document.getElementById(openSubmenuId);
 | |
|     const menuPopup = menu.menupopup;
 | |
|     const menuPopupPromise = BrowserTestUtils.waitForEvent(
 | |
|       menuPopup,
 | |
|       "popupshown"
 | |
|     );
 | |
|     menu.openMenu(true);
 | |
|     await menuPopupPromise;
 | |
|   }
 | |
|   return contextMenu;
 | |
| }
 | |
| 
 | |
| async function hideContentContextMenu() {
 | |
|   const contextMenu = document.getElementById("contentAreaContextMenu");
 | |
|   const awaitPopupHidden = BrowserTestUtils.waitForEvent(
 | |
|     contextMenu,
 | |
|     "popuphidden"
 | |
|   );
 | |
|   contextMenu.hidePopup();
 | |
|   await awaitPopupHidden;
 | |
| }
 |