Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
"use strict";
const { AddonManager } = ChromeUtils.importESModule(
"resource://gre/modules/AddonManager.sys.mjs",
// AddonManager is a singleton, never create two instances of it.
{ global: "shared" }
);
const {
remoteClientManager,
} = require("resource://devtools/client/shared/remote-debugging/remote-client-manager.js");
const {
l10n,
} = require("resource://devtools/client/aboutdebugging/src/modules/l10n.js");
const {
isSupportedDebugTargetPane,
} = require("resource://devtools/client/aboutdebugging/src/modules/debug-target-support.js");
const {
openTemporaryExtension,
} = require("resource://devtools/client/aboutdebugging/src/modules/extensions-helper.js");
const {
getCurrentClient,
getCurrentRuntime,
} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js");
const {
gDevTools,
} = require("resource://devtools/client/framework/devtools.js");
const {
DEBUG_TARGETS,
DEBUG_TARGET_PANE,
REQUEST_EXTENSIONS_FAILURE,
REQUEST_EXTENSIONS_START,
REQUEST_EXTENSIONS_SUCCESS,
REQUEST_PROCESSES_FAILURE,
REQUEST_PROCESSES_START,
REQUEST_PROCESSES_SUCCESS,
REQUEST_TABS_FAILURE,
REQUEST_TABS_START,
REQUEST_TABS_SUCCESS,
REQUEST_WORKERS_FAILURE,
REQUEST_WORKERS_START,
REQUEST_WORKERS_SUCCESS,
TEMPORARY_EXTENSION_INSTALL_FAILURE,
TEMPORARY_EXTENSION_INSTALL_START,
TEMPORARY_EXTENSION_INSTALL_SUCCESS,
TEMPORARY_EXTENSION_RELOAD_FAILURE,
TEMPORARY_EXTENSION_RELOAD_START,
TEMPORARY_EXTENSION_RELOAD_SUCCESS,
TERMINATE_EXTENSION_BGSCRIPT_FAILURE,
TERMINATE_EXTENSION_BGSCRIPT_SUCCESS,
TERMINATE_EXTENSION_BGSCRIPT_START,
RUNTIMES,
} = require("resource://devtools/client/aboutdebugging/src/constants.js");
const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
function getTabForUrl(url) {
for (const navigator of Services.wm.getEnumerator("navigator:browser")) {
for (const browser of navigator.gBrowser.browsers) {
if (
browser.contentWindow &&
browser.contentWindow.location.href === url
) {
return navigator.gBrowser.getTabForBrowser(browser);
}
}
}
return null;
}
function inspectDebugTarget(type, id) {
return async ({ dispatch, getState }) => {
const runtime = getCurrentRuntime(getState().runtimes);
if (
type == DEBUG_TARGETS.EXTENSION &&
runtime.id === RUNTIMES.THIS_FIREFOX
) {
// we are opening the toolbox in an independant window.
// Whereas all others are opened in new tabs.
gDevTools.showToolboxForWebExtension(id);
} else {
const urlParams = {
type,
};
// Main process may not provide any ID.
if (id) {
urlParams.id = id;
}
if (runtime.id !== RUNTIMES.THIS_FIREFOX) {
urlParams.remoteId = remoteClientManager.getRemoteId(
runtime.id,
runtime.type
);
}
const url = `about:devtools-toolbox?${new window.URLSearchParams(
urlParams
)}`;
const existingTab = getTabForUrl(url);
if (existingTab) {
const navigator = existingTab.ownerGlobal;
navigator.gBrowser.selectedTab = existingTab;
navigator.focus();
} else {
window.open(url);
}
}
dispatch(
Actions.recordTelemetryEvent("inspect", {
target_type: type.toUpperCase(),
runtime_type: runtime.type,
})
);
};
}
function installTemporaryExtension() {
const message = l10n.getString(
"about-debugging-tmp-extension-install-message"
);
return async ({ dispatch }) => {
dispatch({ type: TEMPORARY_EXTENSION_INSTALL_START });
const file = await openTemporaryExtension(window, message);
try {
await AddonManager.installTemporaryAddon(file);
dispatch({ type: TEMPORARY_EXTENSION_INSTALL_SUCCESS });
} catch (e) {
dispatch({ type: TEMPORARY_EXTENSION_INSTALL_FAILURE, error: e });
}
};
}
function pushServiceWorker(id, registrationFront) {
return async () => {
try {
// The push button is only available if canDebugServiceWorkers is true.
// With this configuration, `push` should always be called on the
// registration front, and not on the (service) WorkerTargetActor.
await registrationFront.push();
} catch (e) {
console.error(e);
}
};
}
function reloadTemporaryExtension(id) {
return async ({ dispatch, getState }) => {
dispatch({ type: TEMPORARY_EXTENSION_RELOAD_START, id });
const clientWrapper = getCurrentClient(getState().runtimes);
try {
const addonTargetFront = await clientWrapper.getAddon({ id });
await addonTargetFront.reload();
dispatch({ type: TEMPORARY_EXTENSION_RELOAD_SUCCESS, id });
} catch (e) {
const error = typeof e === "string" ? new Error(e) : e;
dispatch({ type: TEMPORARY_EXTENSION_RELOAD_FAILURE, id, error });
}
};
}
function removeTemporaryExtension(id) {
return async ({ getState }) => {
const clientWrapper = getCurrentClient(getState().runtimes);
try {
await clientWrapper.uninstallAddon({ id });
} catch (e) {
console.error(e);
}
};
}
function terminateExtensionBackgroundScript(id) {
return async ({ dispatch, getState }) => {
dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_START, id });
const clientWrapper = getCurrentClient(getState().runtimes);
try {
const addonTargetFront = await clientWrapper.getAddon({ id });
await addonTargetFront.terminateBackgroundScript();
dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_SUCCESS, id });
} catch (e) {
const error = typeof e === "string" ? new Error(e) : e;
dispatch({ type: TERMINATE_EXTENSION_BGSCRIPT_FAILURE, id, error });
}
};
}
function requestTabs() {
return async ({ dispatch, getState }) => {
dispatch({ type: REQUEST_TABS_START });
const runtime = getCurrentRuntime(getState().runtimes);
const clientWrapper = getCurrentClient(getState().runtimes);
try {
const isSupported = isSupportedDebugTargetPane(
runtime.runtimeDetails.info.type,
DEBUG_TARGET_PANE.TAB
);
const tabs = isSupported ? await clientWrapper.listTabs() : [];
// Fetch the favicon for all tabs.
await Promise.all(
tabs.map(descriptorFront => descriptorFront.retrieveFavicon())
);
dispatch({ type: REQUEST_TABS_SUCCESS, tabs });
} catch (e) {
dispatch({ type: REQUEST_TABS_FAILURE, error: e });
}
};
}
function requestExtensions() {
return async ({ dispatch, getState }) => {
dispatch({ type: REQUEST_EXTENSIONS_START });
const runtime = getCurrentRuntime(getState().runtimes);
const clientWrapper = getCurrentClient(getState().runtimes);
try {
const isIconDataURLRequired = runtime.type !== RUNTIMES.THIS_FIREFOX;
const addons = await clientWrapper.listAddons({
iconDataURL: isIconDataURLRequired,
});
const showHiddenAddons = getState().ui.showHiddenAddons;
// Filter out non-debuggable addons as well as hidden ones, unless the dedicated
// preference is set to true.
const extensions = addons.filter(
a => a.debuggable && (!a.hidden || showHiddenAddons)
);
const installedExtensions = extensions.filter(
e => !e.temporarilyInstalled
);
const temporaryExtensions = extensions.filter(
e => e.temporarilyInstalled
);
dispatch({
type: REQUEST_EXTENSIONS_SUCCESS,
installedExtensions: sortTargetsByName(installedExtensions),
temporaryExtensions: sortTargetsByName(temporaryExtensions),
});
} catch (e) {
dispatch({ type: REQUEST_EXTENSIONS_FAILURE, error: e });
}
};
}
function requestProcesses() {
return async ({ dispatch, getState }) => {
dispatch({ type: REQUEST_PROCESSES_START });
const clientWrapper = getCurrentClient(getState().runtimes);
try {
const mainProcessDescriptorFront = await clientWrapper.getMainProcess();
dispatch({
type: REQUEST_PROCESSES_SUCCESS,
mainProcess: {
id: 0,
processFront: mainProcessDescriptorFront,
},
});
} catch (e) {
dispatch({ type: REQUEST_PROCESSES_FAILURE, error: e });
}
};
}
function requestWorkers() {
return async ({ dispatch, getState }) => {
dispatch({ type: REQUEST_WORKERS_START });
const clientWrapper = getCurrentClient(getState().runtimes);
try {
const { otherWorkers, serviceWorkers, sharedWorkers } =
await clientWrapper.listWorkers();
for (const serviceWorker of serviceWorkers) {
const { registrationFront } = serviceWorker;
if (!registrationFront) {
continue;
}
const subscription = await registrationFront.getPushSubscription();
serviceWorker.subscription = subscription;
}
dispatch({
type: REQUEST_WORKERS_SUCCESS,
otherWorkers: sortTargetsByName(otherWorkers),
serviceWorkers: sortTargetsByName(serviceWorkers),
sharedWorkers: sortTargetsByName(sharedWorkers),
});
} catch (e) {
dispatch({ type: REQUEST_WORKERS_FAILURE, error: e });
}
};
}
function startServiceWorker(registrationFront) {
return async () => {
try {
await registrationFront.start();
} catch (e) {
console.error(e);
}
};
}
function sortTargetsByName(targets) {
return targets.sort((target1, target2) => {
// Fallback to empty string in case some targets don't have a valid name.
const name1 = target1.name || "";
const name2 = target2.name || "";
return name1.localeCompare(name2);
});
}
function unregisterServiceWorker(registrationFront) {
return async () => {
try {
await registrationFront.unregister();
} catch (e) {
console.error(e);
}
};
}
module.exports = {
inspectDebugTarget,
installTemporaryExtension,
pushServiceWorker,
reloadTemporaryExtension,
removeTemporaryExtension,
requestTabs,
requestExtensions,
requestProcesses,
requestWorkers,
startServiceWorker,
terminateExtensionBackgroundScript,
unregisterServiceWorker,
};