browser_aboutdebugging_addons_debug_popup.js (8169B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 "use strict"; 4 5 /* import-globals-from helper-addons.js */ 6 Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-addons.js", this); 7 8 /* import-globals-from ../../../inspector/test/shared-head.js */ 9 Services.scriptloader.loadSubScript( 10 "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", 11 this 12 ); 13 14 // There are shutdown issues for which multiple rejections are left uncaught. 15 // See bug 1018184 for resolving these issues. 16 const { PromiseTestUtils } = ChromeUtils.importESModule( 17 "resource://testing-common/PromiseTestUtils.sys.mjs" 18 ); 19 PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/); 20 21 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message. 22 requestLongerTimeout(2); 23 24 const ADDON_ID = "test-devtools-webextension@mozilla.org"; 25 const ADDON_NAME = "test-devtools-webextension"; 26 27 /** 28 * This test file ensures that the webextension addon developer toolbox: 29 * - has a frame list menu and the noautohide toolbar toggle button, and they 30 * can be used to switch the current target to the extension popup page. 31 */ 32 add_task(async function testWebExtensionsToolboxWebConsole() { 33 await enableExtensionDebugging(); 34 35 // Bug 1686922: Disable the error count button to avoid intermittent failures. 36 await pushPref("devtools.command-button-errorcount.enabled", false); 37 38 is( 39 Services.prefs.getBoolPref("ui.popup.disable_autohide"), 40 false, 41 "disable_autohide should be initially false" 42 ); 43 44 const { 45 document, 46 tab, 47 window: aboutDebuggingWindow, 48 } = await openAboutDebugging(); 49 await selectThisFirefoxPage( 50 document, 51 aboutDebuggingWindow.AboutDebugging.store 52 ); 53 54 const extension = await installTestAddon(document); 55 56 const onBackgroundFunctionCalled = waitForExtensionTestMessage( 57 extension, 58 "onBackgroundFunctionCalled" 59 ); 60 const onPopupPageFunctionCalled = waitForExtensionTestMessage( 61 extension, 62 "onPopupPageFunctionCalled" 63 ); 64 65 info("Open a toolbox to debug the addon"); 66 const { devtoolsWindow } = await openAboutDevtoolsToolbox( 67 document, 68 tab, 69 aboutDebuggingWindow, 70 ADDON_NAME 71 ); 72 73 const toolbox = getToolbox(devtoolsWindow); 74 const webconsole = await toolbox.selectTool("webconsole"); 75 76 info("Clicking the menu button to disable autohide"); 77 await disablePopupAutohide(toolbox); 78 79 info("Check that console messages are evaluated in the background context"); 80 const consoleWrapper = webconsole.hud.ui.wrapper; 81 consoleWrapper.dispatchEvaluateExpression("backgroundFunction()"); 82 await onBackgroundFunctionCalled; 83 84 clickOnAddonWidget(ADDON_ID); 85 86 info("Wait until the frames list button is displayed"); 87 const btn = await waitFor(() => 88 toolbox.doc.getElementById("command-button-frames") 89 ); 90 91 info("Clicking the frame list button"); 92 btn.click(); 93 94 const menuList = toolbox.doc.getElementById("toolbox-frame-menu"); 95 await waitFor( 96 () => menuList.querySelectorAll(".command").length == 3, 97 "Wait for fallback, background and popup documents to be listed" 98 ); 99 const frames = Array.from(menuList.querySelectorAll(".command")); 100 is(frames.length, 3, "Has the expected number of frames"); 101 102 const popupFrameBtn = frames 103 .filter(frame => { 104 return frame.querySelector(".label").textContent.endsWith("popup.html"); 105 }) 106 .pop(); 107 108 ok(popupFrameBtn, "Extension Popup frame found in the listed frames"); 109 110 info( 111 "Click on the extension popup frame and wait for a `dom-complete` resource" 112 ); 113 const { onResource: onDomCompleteResource } = 114 await toolbox.commands.resourceCommand.waitForNextResource( 115 toolbox.commands.resourceCommand.TYPES.DOCUMENT_EVENT, 116 { 117 ignoreExistingResources: true, 118 predicate: resource => resource.name === "dom-complete", 119 } 120 ); 121 popupFrameBtn.click(); 122 await onDomCompleteResource; 123 124 info("Execute `popupPageFunction()`"); 125 consoleWrapper.dispatchEvaluateExpression("popupPageFunction()"); 126 127 const args = await onPopupPageFunctionCalled; 128 ok(true, "Received console message from the popup page function as expected"); 129 is(args[0], "onPopupPageFunctionCalled", "Got the expected console message"); 130 is( 131 args[1] && args[1].name, 132 ADDON_NAME, 133 "Got the expected manifest from WebExtension API" 134 ); 135 136 info("Check that node screenshots work in webextension targets"); 137 const inspector = await toolbox.selectTool("inspector"); 138 await selectNode("#node-to-screenshot", inspector); 139 const redScreenshot = await takeNodeScreenshot(inspector); 140 await assertSingleColorScreenshotImage(redScreenshot, 10, 10, { 141 r: 255, 142 g: 0, 143 b: 0, 144 }); 145 146 await closeWebExtAboutDevtoolsToolbox(devtoolsWindow, aboutDebuggingWindow); 147 148 is( 149 Services.prefs.getBoolPref("ui.popup.disable_autohide"), 150 false, 151 "disable_autohide should be reset to false when the toolbox is closed" 152 ); 153 154 await removeTemporaryExtension(ADDON_NAME, document); 155 await removeTab(tab); 156 }); 157 158 /** 159 * Helper to wait for a specific message on an Extension instance. 160 */ 161 function waitForExtensionTestMessage(extension, expectedMessage) { 162 return new Promise(done => { 163 extension.on("test-message", function testLogListener(evt, ...args) { 164 const [message] = args; 165 166 if (message !== expectedMessage) { 167 return; 168 } 169 170 extension.off("test-message", testLogListener); 171 done(args); 172 }); 173 }); 174 } 175 176 /** 177 * Install the addon used for this test. 178 * Returns a Promise that resolve the Extension instance that was just 179 * installed. 180 */ 181 async function installTestAddon(doc) { 182 // Start watching for the extension on the Extension Management before we 183 // install it. 184 const onExtensionReady = waitForExtension(ADDON_NAME); 185 186 // Install the extension. 187 await installTemporaryExtensionFromXPI( 188 { 189 background() { 190 const { browser } = this; 191 window.backgroundFunction = function () { 192 browser.test.sendMessage("onBackgroundFunctionCalled"); 193 }; 194 }, 195 extraProperties: { 196 browser_action: { 197 default_title: "WebExtension Popup Debugging", 198 default_popup: "popup.html", 199 default_area: "navbar", 200 }, 201 }, 202 files: { 203 "popup.html": `<!DOCTYPE html> 204 <html> 205 <head> 206 <meta charset="utf-8"> 207 <script src="popup.js"></script> 208 </head> 209 <body> 210 Background Page Body Test Content 211 <div 212 id="node-to-screenshot" 213 style="height: 10px; width: 10px; background: red;"> 214 </div> 215 </body> 216 </html> 217 `, 218 "popup.js": function () { 219 const { browser } = this; 220 window.popupPageFunction = function () { 221 browser.test.sendMessage( 222 "onPopupPageFunctionCalled", 223 browser.runtime.getManifest() 224 ); 225 }; 226 }, 227 }, 228 id: ADDON_ID, 229 name: ADDON_NAME, 230 }, 231 doc 232 ); 233 234 // The onExtensionReady promise will resolve the extension instance. 235 return onExtensionReady; 236 } 237 238 /** 239 * Helper to retrieve the Extension instance. 240 */ 241 async function waitForExtension(addonName) { 242 const { Management } = ChromeUtils.importESModule( 243 "resource://gre/modules/Extension.sys.mjs" 244 ); 245 246 return new Promise(resolve => { 247 Management.on("startup", function listener(event, extension) { 248 if (extension.name != addonName) { 249 return; 250 } 251 252 Management.off("startup", listener); 253 resolve(extension); 254 }); 255 }); 256 } 257 258 /** 259 * Disables the popup autohide feature, which is mandatory to debug webextension 260 * popups. 261 */ 262 function disablePopupAutohide(toolbox) { 263 return new Promise(resolve => { 264 toolbox.doc.addEventListener( 265 "popupshown", 266 () => { 267 const menuItem = toolbox.doc.getElementById( 268 "toolbox-meatball-menu-noautohide" 269 ); 270 menuItem.click(); 271 resolve(); 272 }, 273 { once: true } 274 ); 275 toolbox.doc.getElementById("toolbox-meatball-menu-button").click(); 276 }); 277 }