debug-targets.js (10516B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { AddonManager } = ChromeUtils.importESModule( 8 "resource://gre/modules/AddonManager.sys.mjs", 9 // AddonManager is a singleton, never create two instances of it. 10 { global: "shared" } 11 ); 12 const { 13 remoteClientManager, 14 } = require("resource://devtools/client/shared/remote-debugging/remote-client-manager.js"); 15 16 const { 17 l10n, 18 } = require("resource://devtools/client/aboutdebugging/src/modules/l10n.js"); 19 20 const { 21 isSupportedDebugTargetPane, 22 } = require("resource://devtools/client/aboutdebugging/src/modules/debug-target-support.js"); 23 24 const { 25 openTemporaryExtension, 26 } = require("resource://devtools/client/aboutdebugging/src/modules/extensions-helper.js"); 27 28 const { 29 getCurrentClient, 30 getCurrentRuntime, 31 } = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js"); 32 33 const { 34 gDevTools, 35 } = require("resource://devtools/client/framework/devtools.js"); 36 37 const { 38 DEBUG_TARGETS, 39 DEBUG_TARGET_PANE, 40 REQUEST_EXTENSIONS_FAILURE, 41 REQUEST_EXTENSIONS_START, 42 REQUEST_EXTENSIONS_SUCCESS, 43 REQUEST_PROCESSES_FAILURE, 44 REQUEST_PROCESSES_START, 45 REQUEST_PROCESSES_SUCCESS, 46 REQUEST_TABS_FAILURE, 47 REQUEST_TABS_START, 48 REQUEST_TABS_SUCCESS, 49 REQUEST_WORKERS_FAILURE, 50 REQUEST_WORKERS_START, 51 REQUEST_WORKERS_SUCCESS, 52 TEMPORARY_EXTENSION_INSTALL_FAILURE, 53 TEMPORARY_EXTENSION_INSTALL_START, 54 TEMPORARY_EXTENSION_INSTALL_SUCCESS, 55 TEMPORARY_EXTENSION_RELOAD_FAILURE, 56 TEMPORARY_EXTENSION_RELOAD_START, 57 TEMPORARY_EXTENSION_RELOAD_SUCCESS, 58 TERMINATE_EXTENSION_BGSCRIPT_FAILURE, 59 TERMINATE_EXTENSION_BGSCRIPT_SUCCESS, 60 TERMINATE_EXTENSION_BGSCRIPT_START, 61 RUNTIMES, 62 } = require("resource://devtools/client/aboutdebugging/src/constants.js"); 63 64 const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js"); 65 66 function getTabForUrl(url) { 67 for (const navigator of Services.wm.getEnumerator("navigator:browser")) { 68 for (const browser of navigator.gBrowser.browsers) { 69 if ( 70 browser.contentWindow && 71 browser.contentWindow.location.href === url 72 ) { 73 return navigator.gBrowser.getTabForBrowser(browser); 74 } 75 } 76 } 77 78 return null; 79 } 80 81 function inspectDebugTarget(type, id) { 82 return async ({ dispatch, getState }) => { 83 const runtime = getCurrentRuntime(getState().runtimes); 84 85 if ( 86 type == DEBUG_TARGETS.EXTENSION && 87 runtime.id === RUNTIMES.THIS_FIREFOX 88 ) { 89 // Bug 1780912: To avoid UX issues when debugging local web extensions, 90 // we are opening the toolbox in an independant window. 91 // Whereas all others are opened in new tabs. 92 gDevTools.showToolboxForWebExtension(id); 93 } else { 94 const urlParams = { 95 type, 96 }; 97 // Main process may not provide any ID. 98 if (id) { 99 urlParams.id = id; 100 } 101 102 if (runtime.id !== RUNTIMES.THIS_FIREFOX) { 103 urlParams.remoteId = remoteClientManager.getRemoteId( 104 runtime.id, 105 runtime.type 106 ); 107 } 108 109 const url = `about:devtools-toolbox?${new window.URLSearchParams( 110 urlParams 111 )}`; 112 113 const existingTab = getTabForUrl(url); 114 if (existingTab) { 115 const navigator = existingTab.ownerGlobal; 116 navigator.gBrowser.selectedTab = existingTab; 117 navigator.focus(); 118 } else { 119 window.open(url); 120 } 121 } 122 123 dispatch( 124 Actions.recordTelemetryEvent("inspect", { 125 target_type: type.toUpperCase(), 126 runtime_type: runtime.type, 127 }) 128 ); 129 }; 130 } 131 132 function installTemporaryExtension() { 133 const message = l10n.getString( 134 "about-debugging-tmp-extension-install-message" 135 ); 136 return async ({ dispatch }) => { 137 dispatch({ type: TEMPORARY_EXTENSION_INSTALL_START }); 138 const file = await openTemporaryExtension(window, message); 139 try { 140 await AddonManager.installTemporaryAddon(file); 141 dispatch({ type: TEMPORARY_EXTENSION_INSTALL_SUCCESS }); 142 } catch (e) { 143 dispatch({ type: TEMPORARY_EXTENSION_INSTALL_FAILURE, error: e }); 144 } 145 }; 146 } 147 148 function pushServiceWorker(id, registrationFront) { 149 return async () => { 150 try { 151 // The push button is only available if canDebugServiceWorkers is true. 152 // With this configuration, `push` should always be called on the 153 // registration front, and not on the (service) WorkerTargetActor. 154 await registrationFront.push(); 155 } catch (e) { 156 console.error(e); 157 } 158 }; 159 } 160 161 function reloadTemporaryExtension(id) { 162 return async ({ dispatch, getState }) => { 163 dispatch({ type: TEMPORARY_EXTENSION_RELOAD_START, id }); 164 const clientWrapper = getCurrentClient(getState().runtimes); 165 166 try { 167 const addonTargetFront = await clientWrapper.getAddon({ id }); 168 await addonTargetFront.reload(); 169 dispatch({ type: TEMPORARY_EXTENSION_RELOAD_SUCCESS, id }); 170 } catch (e) { 171 const error = typeof e === "string" ? new Error(e) : e; 172 dispatch({ type: TEMPORARY_EXTENSION_RELOAD_FAILURE, id, error }); 173 } 174 }; 175 } 176 177 function removeTemporaryExtension(id) { 178 return async ({ getState }) => { 179 const clientWrapper = getCurrentClient(getState().runtimes); 180 181 try { 182 await clientWrapper.uninstallAddon({ id }); 183 } catch (e) { 184 console.error(e); 185 } 186 }; 187 } 188 189 function terminateExtensionBackgroundScript(id) { 190 return async ({ dispatch, getState }) => { 191 dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_START, id }); 192 const clientWrapper = getCurrentClient(getState().runtimes); 193 194 try { 195 const addonTargetFront = await clientWrapper.getAddon({ id }); 196 await addonTargetFront.terminateBackgroundScript(); 197 dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_SUCCESS, id }); 198 } catch (e) { 199 const error = typeof e === "string" ? new Error(e) : e; 200 dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_FAILURE, id, error }); 201 } 202 }; 203 } 204 205 function requestTabs() { 206 return async ({ dispatch, getState }) => { 207 dispatch({ type: REQUEST_TABS_START }); 208 209 const runtime = getCurrentRuntime(getState().runtimes); 210 const clientWrapper = getCurrentClient(getState().runtimes); 211 212 try { 213 const isSupported = isSupportedDebugTargetPane( 214 runtime.runtimeDetails.info.type, 215 DEBUG_TARGET_PANE.TAB 216 ); 217 const tabs = isSupported ? await clientWrapper.listTabs() : []; 218 219 // Fetch the favicon for all tabs. 220 await Promise.all( 221 tabs.map(descriptorFront => descriptorFront.retrieveFavicon()) 222 ); 223 224 dispatch({ type: REQUEST_TABS_SUCCESS, tabs }); 225 } catch (e) { 226 dispatch({ type: REQUEST_TABS_FAILURE, error: e }); 227 } 228 }; 229 } 230 231 function requestExtensions() { 232 return async ({ dispatch, getState }) => { 233 dispatch({ type: REQUEST_EXTENSIONS_START }); 234 235 const runtime = getCurrentRuntime(getState().runtimes); 236 const clientWrapper = getCurrentClient(getState().runtimes); 237 238 try { 239 const isIconDataURLRequired = runtime.type !== RUNTIMES.THIS_FIREFOX; 240 const addons = await clientWrapper.listAddons({ 241 iconDataURL: isIconDataURLRequired, 242 }); 243 244 const showHiddenAddons = getState().ui.showHiddenAddons; 245 246 // Filter out non-debuggable addons as well as hidden ones, unless the dedicated 247 // preference is set to true. 248 const extensions = addons.filter( 249 a => a.debuggable && (!a.hidden || showHiddenAddons) 250 ); 251 252 const installedExtensions = extensions.filter( 253 e => !e.temporarilyInstalled 254 ); 255 const temporaryExtensions = extensions.filter( 256 e => e.temporarilyInstalled 257 ); 258 259 dispatch({ 260 type: REQUEST_EXTENSIONS_SUCCESS, 261 installedExtensions: sortTargetsByName(installedExtensions), 262 temporaryExtensions: sortTargetsByName(temporaryExtensions), 263 }); 264 } catch (e) { 265 dispatch({ type: REQUEST_EXTENSIONS_FAILURE, error: e }); 266 } 267 }; 268 } 269 270 function requestProcesses() { 271 return async ({ dispatch, getState }) => { 272 dispatch({ type: REQUEST_PROCESSES_START }); 273 274 const clientWrapper = getCurrentClient(getState().runtimes); 275 276 try { 277 const mainProcessDescriptorFront = await clientWrapper.getMainProcess(); 278 dispatch({ 279 type: REQUEST_PROCESSES_SUCCESS, 280 mainProcess: { 281 id: 0, 282 processFront: mainProcessDescriptorFront, 283 }, 284 }); 285 } catch (e) { 286 dispatch({ type: REQUEST_PROCESSES_FAILURE, error: e }); 287 } 288 }; 289 } 290 291 function requestWorkers() { 292 return async ({ dispatch, getState }) => { 293 dispatch({ type: REQUEST_WORKERS_START }); 294 295 const clientWrapper = getCurrentClient(getState().runtimes); 296 297 try { 298 const { otherWorkers, serviceWorkers, sharedWorkers } = 299 await clientWrapper.listWorkers(); 300 301 for (const serviceWorker of serviceWorkers) { 302 const { registrationFront } = serviceWorker; 303 if (!registrationFront) { 304 continue; 305 } 306 307 const subscription = await registrationFront.getPushSubscription(); 308 serviceWorker.subscription = subscription; 309 } 310 311 dispatch({ 312 type: REQUEST_WORKERS_SUCCESS, 313 otherWorkers: sortTargetsByName(otherWorkers), 314 serviceWorkers: sortTargetsByName(serviceWorkers), 315 sharedWorkers: sortTargetsByName(sharedWorkers), 316 }); 317 } catch (e) { 318 dispatch({ type: REQUEST_WORKERS_FAILURE, error: e }); 319 } 320 }; 321 } 322 323 function startServiceWorker(registrationFront) { 324 return async () => { 325 try { 326 await registrationFront.start(); 327 } catch (e) { 328 console.error(e); 329 } 330 }; 331 } 332 333 function sortTargetsByName(targets) { 334 return targets.sort((target1, target2) => { 335 // Fallback to empty string in case some targets don't have a valid name. 336 const name1 = target1.name || ""; 337 const name2 = target2.name || ""; 338 return name1.localeCompare(name2); 339 }); 340 } 341 342 function unregisterServiceWorker(registrationFront) { 343 return async () => { 344 try { 345 await registrationFront.unregister(); 346 } catch (e) { 347 console.error(e); 348 } 349 }; 350 } 351 352 module.exports = { 353 inspectDebugTarget, 354 installTemporaryExtension, 355 pushServiceWorker, 356 reloadTemporaryExtension, 357 removeTemporaryExtension, 358 requestTabs, 359 requestExtensions, 360 requestProcesses, 361 requestWorkers, 362 startServiceWorker, 363 terminateExtensionBackgroundScript, 364 unregisterServiceWorker, 365 };