browser_menu_api.js (6861B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test that the Menu API works 7 8 const URL = "data:text/html;charset=utf8,test page for menu api"; 9 const Menu = require("resource://devtools/client/framework/menu.js"); 10 const MenuItem = require("resource://devtools/client/framework/menu-item.js"); 11 12 add_task(async function () { 13 info("Create a test tab and open the toolbox"); 14 const tab = await addTab(URL); 15 const toolbox = await gDevTools.showToolboxForTab(tab, { 16 toolId: "webconsole", 17 }); 18 19 // This test will involve localized strings, make sure the necessary FTL file is 20 // available in the toolbox top window. 21 toolbox.topWindow.MozXULElement.insertFTLIfNeeded( 22 "toolkit/global/textActions.ftl" 23 ); 24 25 loadFTL(toolbox, "toolkit/global/textActions.ftl"); 26 27 await testMenuItems(); 28 await testMenuPopup(toolbox); 29 await testSubmenu(toolbox); 30 }); 31 32 function testMenuItems() { 33 const menu = new Menu(); 34 const menuItem1 = new MenuItem(); 35 const menuItem2 = new MenuItem(); 36 37 menu.append(menuItem1); 38 menu.append(menuItem2); 39 40 is(menu.items.length, 2, "Correct number of 'items'"); 41 is(menu.items[0], menuItem1, "Correct reference to MenuItem"); 42 is(menu.items[1], menuItem2, "Correct reference to MenuItem"); 43 } 44 45 async function testMenuPopup(toolbox) { 46 let clickFired = false; 47 48 const menu = new Menu({ 49 id: "menu-popup", 50 }); 51 menu.append(new MenuItem({ type: "separator" })); 52 53 const MENU_ITEMS = [ 54 new MenuItem({ 55 id: "menu-item-1", 56 label: "Normal Item", 57 click: () => { 58 info("Click callback has fired for menu item"); 59 clickFired = true; 60 }, 61 }), 62 new MenuItem({ 63 label: "Checked Item", 64 type: "checkbox", 65 checked: true, 66 }), 67 new MenuItem({ 68 label: "Radio Item", 69 type: "radio", 70 }), 71 new MenuItem({ 72 label: "Disabled Item", 73 disabled: true, 74 }), 75 new MenuItem({ 76 l10nID: "text-action-undo", 77 }), 78 ]; 79 80 for (const item of MENU_ITEMS) { 81 menu.append(item); 82 } 83 84 // Append an invisible MenuItem, which shouldn't show up in the DOM 85 menu.append( 86 new MenuItem({ 87 label: "Invisible", 88 visible: false, 89 }) 90 ); 91 92 menu.popup(0, 0, toolbox.doc); 93 const popup = toolbox.topDoc.querySelector("#menu-popup"); 94 ok(popup, "A popup is in the DOM"); 95 96 const menuSeparators = toolbox.topDoc.querySelectorAll( 97 "#menu-popup > menuseparator" 98 ); 99 is(menuSeparators.length, 1, "A separator is in the menu"); 100 101 const menuItems = toolbox.topDoc.querySelectorAll("#menu-popup > menuitem"); 102 is(menuItems.length, MENU_ITEMS.length, "Correct number of menuitems"); 103 104 is(menuItems[0].id, MENU_ITEMS[0].id, "Correct id for menuitem"); 105 is(menuItems[0].getAttribute("label"), MENU_ITEMS[0].label, "Correct label"); 106 107 is(menuItems[1].getAttribute("label"), MENU_ITEMS[1].label, "Correct label"); 108 is(menuItems[1].getAttribute("type"), "checkbox", "Correct type attr"); 109 ok(menuItems[1].hasAttribute("checked"), "Has checked attr"); 110 111 is(menuItems[2].getAttribute("label"), MENU_ITEMS[2].label, "Correct label"); 112 is(menuItems[2].getAttribute("type"), "radio", "Correct type attr"); 113 ok(!menuItems[2].hasAttribute("checked"), "Doesn't have checked attr"); 114 115 is(menuItems[3].getAttribute("label"), MENU_ITEMS[3].label, "Correct label"); 116 ok(menuItems[3].hasAttribute("disabled"), "disabled attr menuitem"); 117 118 is( 119 menuItems[4].getAttribute("data-l10n-id"), 120 MENU_ITEMS[4].l10nID, 121 "Correct localization attribute" 122 ); 123 124 await once(menu, "open"); 125 const closed = once(menu, "close"); 126 popup.activateItem(menuItems[0]); 127 await closed; 128 ok(clickFired, "Click has fired"); 129 130 ok( 131 !toolbox.topDoc.querySelector("#menu-popup"), 132 "Popup removed from the DOM" 133 ); 134 } 135 136 async function testSubmenu(toolbox) { 137 let clickFired = false; 138 const menu = new Menu({ 139 id: "menu-popup", 140 }); 141 const submenu = new Menu({ 142 id: "submenu-popup", 143 }); 144 submenu.append( 145 new MenuItem({ 146 label: "Submenu item", 147 click: () => { 148 info("Click callback has fired for submenu item"); 149 clickFired = true; 150 }, 151 }) 152 ); 153 menu.append( 154 new MenuItem({ 155 l10nID: "text-action-copy", 156 submenu, 157 }) 158 ); 159 menu.append( 160 new MenuItem({ 161 label: "Submenu parent with attributes", 162 id: "submenu-parent-with-attrs", 163 submenu, 164 accesskey: "A", 165 disabled: true, 166 }) 167 ); 168 169 menu.popup(0, 0, toolbox.doc); 170 const popup = toolbox.topDoc.querySelector("#menu-popup"); 171 ok(popup, "A popup is in the DOM"); 172 is( 173 toolbox.topDoc.querySelectorAll("#menu-popup > menuitem").length, 174 0, 175 "No menuitem children" 176 ); 177 178 const menus = toolbox.topDoc.querySelectorAll("#menu-popup > menu"); 179 is(menus.length, 2, "Correct number of menus"); 180 ok( 181 !menus[0].hasAttribute("label"), 182 "No label: should be set by localization" 183 ); 184 ok(!menus[0].hasAttribute("disabled"), "Correct disabled state"); 185 is( 186 menus[0].getAttribute("data-l10n-id"), 187 "text-action-copy", 188 "Correct localization attribute" 189 ); 190 191 is(menus[1].getAttribute("accesskey"), "A", "Correct accesskey"); 192 ok(menus[1].hasAttribute("disabled"), "Correct disabled state"); 193 is(menus[1].id, "submenu-parent-with-attrs", "Correct id"); 194 195 const subMenuItems = menus[0].querySelectorAll("menupopup > menuitem"); 196 is(subMenuItems.length, 1, "Correct number of submenu items"); 197 is(subMenuItems[0].getAttribute("label"), "Submenu item", "Correct label"); 198 199 await once(menu, "open"); 200 const closed = once(menu, "close"); 201 202 // The following section tests keyboard navigation of the context menus. 203 // This doesn't work on macOS when native context menus are enabled. 204 if (Services.prefs.getBoolPref("widget.macos.native-context-menus", false)) { 205 info("Using openMenu semantics because of macOS native context menus."); 206 let shown = once(menus[0], "popupshown"); 207 menus[0].openMenu(true); 208 await shown; 209 210 const hidden = once(menus[0], "popuphidden"); 211 menus[0].openMenu(false); 212 await hidden; 213 214 shown = once(menus[0], "popupshown"); 215 menus[0].openMenu(true); 216 await shown; 217 } else { 218 info("Using keyboard navigation to open, close, and reopen the submenu"); 219 let shown = once(menus[0], "popupshown"); 220 EventUtils.synthesizeKey("KEY_ArrowDown"); 221 EventUtils.synthesizeKey("KEY_ArrowRight"); 222 await shown; 223 224 const hidden = once(menus[0], "popuphidden"); 225 EventUtils.synthesizeKey("KEY_ArrowLeft"); 226 await hidden; 227 228 shown = once(menus[0], "popupshown"); 229 EventUtils.synthesizeKey("KEY_ArrowRight"); 230 await shown; 231 } 232 233 info("Clicking the submenu item"); 234 const subMenu = subMenuItems[0].closest("menupopup"); 235 subMenu.activateItem(subMenuItems[0]); 236 237 await closed; 238 ok(clickFired, "Click has fired"); 239 }