summaryrefslogtreecommitdiffstats
path: root/devtools/client/aboutdebugging/src/middleware
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/aboutdebugging/src/middleware
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/aboutdebugging/src/middleware')
-rw-r--r--devtools/client/aboutdebugging/src/middleware/debug-target-listener.js111
-rw-r--r--devtools/client/aboutdebugging/src/middleware/error-logging.js35
-rw-r--r--devtools/client/aboutdebugging/src/middleware/event-recording.js268
-rw-r--r--devtools/client/aboutdebugging/src/middleware/extension-component-data.js84
-rw-r--r--devtools/client/aboutdebugging/src/middleware/moz.build13
-rw-r--r--devtools/client/aboutdebugging/src/middleware/process-component-data.js55
-rw-r--r--devtools/client/aboutdebugging/src/middleware/tab-component-data.js51
-rw-r--r--devtools/client/aboutdebugging/src/middleware/worker-component-data.js82
8 files changed, 699 insertions, 0 deletions
diff --git a/devtools/client/aboutdebugging/src/middleware/debug-target-listener.js b/devtools/client/aboutdebugging/src/middleware/debug-target-listener.js
new file mode 100644
index 0000000000..655e667a6d
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/debug-target-listener.js
@@ -0,0 +1,111 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ EXTENSION_BGSCRIPT_STATUSES,
+ EXTENSION_BGSCRIPT_STATUS_UPDATED,
+ UNWATCH_RUNTIME_START,
+ WATCH_RUNTIME_SUCCESS,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
+
+const RootResourceCommand = require("resource://devtools/shared/commands/root-resource/root-resource-command.js");
+
+function debugTargetListenerMiddleware(store) {
+ const onExtensionsUpdated = () => {
+ store.dispatch(Actions.requestExtensions());
+ };
+
+ const onTabsUpdated = () => {
+ store.dispatch(Actions.requestTabs());
+ };
+
+ const onWorkersUpdated = () => {
+ store.dispatch(Actions.requestWorkers());
+ };
+
+ let rootResourceCommand;
+
+ function onExtensionsBackgroundScriptStatusAvailable(resources) {
+ for (const resource of resources) {
+ const backgroundScriptStatus = resource.payload.isRunning
+ ? EXTENSION_BGSCRIPT_STATUSES.RUNNING
+ : EXTENSION_BGSCRIPT_STATUSES.STOPPED;
+
+ store.dispatch({
+ type: EXTENSION_BGSCRIPT_STATUS_UPDATED,
+ id: resource.payload.addonId,
+ backgroundScriptStatus,
+ });
+ }
+ }
+
+ return next => async action => {
+ switch (action.type) {
+ case WATCH_RUNTIME_SUCCESS: {
+ const { runtime } = action;
+ const { clientWrapper } = runtime.runtimeDetails;
+
+ rootResourceCommand = clientWrapper.createRootResourceCommand();
+
+ // Watch extensions background script status updates.
+ await rootResourceCommand
+ .watchResources(
+ [RootResourceCommand.TYPES.EXTENSIONS_BGSCRIPT_STATUS],
+ { onAvailable: onExtensionsBackgroundScriptStatusAvailable }
+ )
+ .catch(e => {
+ // Log an error if watching this resource rejects (e.g. if
+ // the promise was not resolved yet when about:debugging tab
+ // or the RDP connection to a remote target has been closed).
+ console.error(e);
+ });
+
+ // Tabs
+ clientWrapper.on("tabListChanged", onTabsUpdated);
+
+ // Addons
+ clientWrapper.on("addonListChanged", onExtensionsUpdated);
+
+ // Workers
+ clientWrapper.on("workersUpdated", onWorkersUpdated);
+ break;
+ }
+ case UNWATCH_RUNTIME_START: {
+ const { runtime } = action;
+ const { clientWrapper } = runtime.runtimeDetails;
+
+ // Stop watching extensions background script status updates.
+ try {
+ rootResourceCommand?.unwatchResources(
+ [RootResourceCommand.TYPES.EXTENSIONS_BGSCRIPT_STATUS],
+ { onAvailable: onExtensionsBackgroundScriptStatusAvailable }
+ );
+ } catch (e) {
+ // Log an error if watching this resource rejects (e.g. if
+ // the promise was not resolved yet when about:debugging tab
+ // or the RDP connection to a remote target has been closed).
+ console.error(e);
+ }
+
+ // Tabs
+ clientWrapper.off("tabListChanged", onTabsUpdated);
+
+ // Addons
+ clientWrapper.off("addonListChanged", onExtensionsUpdated);
+
+ // Workers
+ clientWrapper.off("workersUpdated", onWorkersUpdated);
+ break;
+ }
+ }
+
+ return next(action);
+ };
+}
+
+module.exports = debugTargetListenerMiddleware;
diff --git a/devtools/client/aboutdebugging/src/middleware/error-logging.js b/devtools/client/aboutdebugging/src/middleware/error-logging.js
new file mode 100644
index 0000000000..fd04f34b76
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/error-logging.js
@@ -0,0 +1,35 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Error logging middleware that will forward all actions that contain an error property
+ * to the console.
+ */
+function errorLoggingMiddleware() {
+ return next => action => {
+ if (action.error) {
+ const { error } = action;
+ if (error.message) {
+ console.error(`[ACTION FAILED] ${action.type}: ${error.message}`);
+ } else if (typeof error === "string") {
+ // All failure actions should dispatch an error object instead of a message.
+ // We allow some flexibility to still provide some error logging.
+ console.error(`[ACTION FAILED] ${action.type}: ${error}`);
+ console.error(
+ `[ACTION FAILED] ${action.type} should dispatch the error object!`
+ );
+ }
+
+ if (error.stack) {
+ console.error(error.stack);
+ }
+ }
+
+ return next(action);
+ };
+}
+
+module.exports = errorLoggingMiddleware;
diff --git a/devtools/client/aboutdebugging/src/middleware/event-recording.js b/devtools/client/aboutdebugging/src/middleware/event-recording.js
new file mode 100644
index 0000000000..f926100b8b
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/event-recording.js
@@ -0,0 +1,268 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Telemetry = require("resource://devtools/client/shared/telemetry.js");
+loader.lazyGetter(
+ this,
+ "telemetry",
+ () => new Telemetry({ useSessionId: true })
+);
+
+const {
+ CONNECT_RUNTIME_CANCEL,
+ CONNECT_RUNTIME_FAILURE,
+ CONNECT_RUNTIME_NOT_RESPONDING,
+ CONNECT_RUNTIME_START,
+ CONNECT_RUNTIME_SUCCESS,
+ DISCONNECT_RUNTIME_SUCCESS,
+ REMOTE_RUNTIMES_UPDATED,
+ RUNTIMES,
+ SELECT_PAGE_SUCCESS,
+ SHOW_PROFILER_DIALOG,
+ TELEMETRY_RECORD,
+ UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+const {
+ findRuntimeById,
+ getAllRuntimes,
+ getCurrentRuntime,
+} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js");
+
+function recordEvent(method, details) {
+ telemetry.recordEvent(method, "aboutdebugging", null, details);
+
+ // For close and open events, also ping the regular telemetry helpers used
+ // for all DevTools UIs.
+ if (method === "open_adbg") {
+ telemetry.toolOpened("aboutdebugging", window.AboutDebugging);
+ } else if (method === "close_adbg") {
+ // XXX: Note that aboutdebugging has no histogram created for
+ // TIME_ACTIVE_SECOND, so calling toolClosed will not actually
+ // record anything.
+ telemetry.toolClosed("aboutdebugging", window.AboutDebugging);
+ }
+}
+
+const telemetryRuntimeIds = new Map();
+// Create an anonymous id that will allow to track all events related to a runtime without
+// leaking personal data related to this runtime.
+function getTelemetryRuntimeId(id) {
+ if (!telemetryRuntimeIds.has(id)) {
+ const randomId = (Math.random() * 100000) | 0;
+ telemetryRuntimeIds.set(id, "runtime-" + randomId);
+ }
+ return telemetryRuntimeIds.get(id);
+}
+
+function getCurrentRuntimeIdForTelemetry(store) {
+ const id = getCurrentRuntime(store.getState().runtimes).id;
+ return getTelemetryRuntimeId(id);
+}
+
+function getRuntimeEventExtras(runtime) {
+ const { extra, runtimeDetails } = runtime;
+
+ // deviceName can be undefined for non-usb devices, but we should not log "undefined".
+ const deviceName = extra?.deviceName || "";
+ const runtimeShortName = runtime.type === RUNTIMES.USB ? runtime.name : "";
+ const runtimeName = runtimeDetails?.info.name || "";
+ return {
+ connection_type: runtime.type,
+ device_name: deviceName,
+ runtime_id: getTelemetryRuntimeId(runtime.id),
+ runtime_name: runtimeName || runtimeShortName,
+ };
+}
+
+function onConnectRuntimeSuccess(action, store) {
+ if (action.runtime.type === RUNTIMES.THIS_FIREFOX) {
+ // Only record connection and disconnection events for remote runtimes.
+ return;
+ }
+ // When we just connected to a runtime, the runtimeDetails are not in the store yet,
+ // so we merge it here to retrieve the expected telemetry data.
+ const storeRuntime = findRuntimeById(
+ action.runtime.id,
+ store.getState().runtimes
+ );
+ const runtime = Object.assign({}, storeRuntime, {
+ runtimeDetails: action.runtime.runtimeDetails,
+ });
+ const extras = Object.assign({}, getRuntimeEventExtras(runtime), {
+ runtime_os: action.runtime.runtimeDetails.info.os,
+ runtime_version: action.runtime.runtimeDetails.info.version,
+ });
+ recordEvent("runtime_connected", extras);
+}
+
+function onDisconnectRuntimeSuccess(action, store) {
+ const runtime = findRuntimeById(action.runtime.id, store.getState().runtimes);
+ if (runtime.type === RUNTIMES.THIS_FIREFOX) {
+ // Only record connection and disconnection events for remote runtimes.
+ return;
+ }
+
+ recordEvent("runtime_disconnected", getRuntimeEventExtras(runtime));
+}
+
+function onRemoteRuntimesUpdated(action, store) {
+ // Compare new runtimes with the existing runtimes to detect if runtimes, devices
+ // have been added or removed.
+ const newRuntimes = action.runtimes;
+ const allRuntimes = getAllRuntimes(store.getState().runtimes);
+ const oldRuntimes = allRuntimes.filter(r => r.type === action.runtimeType);
+
+ // Check if all the old runtimes and devices are still available in the updated
+ // array.
+ for (const oldRuntime of oldRuntimes) {
+ const runtimeRemoved = newRuntimes.every(r => r.id !== oldRuntime.id);
+ if (runtimeRemoved && !oldRuntime.isUnplugged) {
+ recordEvent("runtime_removed", getRuntimeEventExtras(oldRuntime));
+ }
+ }
+
+ // Using device names as unique IDs is inaccurate. See Bug 1544582.
+ const oldDeviceNames = new Set(oldRuntimes.map(r => r.extra.deviceName));
+ for (const oldDeviceName of oldDeviceNames) {
+ const newRuntime = newRuntimes.find(
+ r => r.extra.deviceName === oldDeviceName
+ );
+ const oldRuntime = oldRuntimes.find(
+ r => r.extra.deviceName === oldDeviceName
+ );
+ const isUnplugged = newRuntime?.isUnplugged && !oldRuntime.isUnplugged;
+ if (oldDeviceName && (!newRuntime || isUnplugged)) {
+ recordEvent("device_removed", {
+ connection_type: action.runtimeType,
+ device_name: oldDeviceName,
+ });
+ }
+ }
+
+ // Check if the new runtimes and devices were already available in the existing
+ // array.
+ for (const newRuntime of newRuntimes) {
+ const runtimeAdded = oldRuntimes.every(r => r.id !== newRuntime.id);
+ if (runtimeAdded && !newRuntime.isUnplugged) {
+ recordEvent("runtime_added", getRuntimeEventExtras(newRuntime));
+ }
+ }
+
+ // Using device names as unique IDs is inaccurate. See Bug 1544582.
+ const newDeviceNames = new Set(newRuntimes.map(r => r.extra.deviceName));
+ for (const newDeviceName of newDeviceNames) {
+ const newRuntime = newRuntimes.find(
+ r => r.extra.deviceName === newDeviceName
+ );
+ const oldRuntime = oldRuntimes.find(
+ r => r.extra.deviceName === newDeviceName
+ );
+ const isPlugged = oldRuntime?.isUnplugged && !newRuntime.isUnplugged;
+
+ if (newDeviceName && (!oldRuntime || isPlugged)) {
+ recordEvent("device_added", {
+ connection_type: action.runtimeType,
+ device_name: newDeviceName,
+ });
+ }
+ }
+}
+
+function recordConnectionAttempt(connectionId, runtimeId, status, store) {
+ const runtime = findRuntimeById(runtimeId, store.getState().runtimes);
+ if (runtime.type === RUNTIMES.THIS_FIREFOX) {
+ // Only record connection_attempt events for remote runtimes.
+ return;
+ }
+
+ recordEvent("connection_attempt", {
+ connection_id: connectionId,
+ connection_type: runtime.type,
+ runtime_id: getTelemetryRuntimeId(runtimeId),
+ status,
+ });
+}
+
+/**
+ * This middleware will record events to telemetry for some specific actions.
+ */
+function eventRecordingMiddleware(store) {
+ return next => action => {
+ switch (action.type) {
+ case CONNECT_RUNTIME_CANCEL:
+ recordConnectionAttempt(
+ action.connectionId,
+ action.id,
+ "cancelled",
+ store
+ );
+ break;
+ case CONNECT_RUNTIME_FAILURE:
+ recordConnectionAttempt(
+ action.connectionId,
+ action.id,
+ "failed",
+ store
+ );
+ break;
+ case CONNECT_RUNTIME_NOT_RESPONDING:
+ recordConnectionAttempt(
+ action.connectionId,
+ action.id,
+ "not responding",
+ store
+ );
+ break;
+ case CONNECT_RUNTIME_START:
+ recordConnectionAttempt(action.connectionId, action.id, "start", store);
+ break;
+ case CONNECT_RUNTIME_SUCCESS:
+ recordConnectionAttempt(
+ action.connectionId,
+ action.runtime.id,
+ "success",
+ store
+ );
+ onConnectRuntimeSuccess(action, store);
+ break;
+ case DISCONNECT_RUNTIME_SUCCESS:
+ onDisconnectRuntimeSuccess(action, store);
+ break;
+ case REMOTE_RUNTIMES_UPDATED:
+ onRemoteRuntimesUpdated(action, store);
+ break;
+ case SELECT_PAGE_SUCCESS:
+ recordEvent("select_page", { page_type: action.page });
+ break;
+ case SHOW_PROFILER_DIALOG:
+ recordEvent("show_profiler", {
+ runtime_id: getCurrentRuntimeIdForTelemetry(store),
+ });
+ break;
+ case TELEMETRY_RECORD:
+ const { method, details } = action;
+ if (method) {
+ recordEvent(method, details);
+ } else {
+ console.error(
+ `[RECORD EVENT FAILED] ${action.type}: no "method" property`
+ );
+ }
+ break;
+ case UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS:
+ recordEvent("update_conn_prompt", {
+ prompt_enabled: `${action.connectionPromptEnabled}`,
+ runtime_id: getCurrentRuntimeIdForTelemetry(store),
+ });
+ break;
+ }
+
+ return next(action);
+ };
+}
+
+module.exports = eventRecordingMiddleware;
diff --git a/devtools/client/aboutdebugging/src/middleware/extension-component-data.js b/devtools/client/aboutdebugging/src/middleware/extension-component-data.js
new file mode 100644
index 0000000000..5987f36398
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/extension-component-data.js
@@ -0,0 +1,84 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ DEBUG_TARGETS,
+ REQUEST_EXTENSIONS_SUCCESS,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+const {
+ getExtensionUuid,
+ parseFileUri,
+} = require("resource://devtools/client/aboutdebugging/src/modules/extensions-helper.js");
+
+/**
+ * This middleware converts extensions object that get from DevToolsClient.listAddons()
+ * to data which is used in DebugTargetItem.
+ */
+const extensionComponentDataMiddleware = store => next => action => {
+ switch (action.type) {
+ case REQUEST_EXTENSIONS_SUCCESS: {
+ action.installedExtensions = toComponentData(action.installedExtensions);
+ action.temporaryExtensions = toComponentData(action.temporaryExtensions);
+ break;
+ }
+ }
+
+ return next(action);
+};
+
+function getFilePath(extension) {
+ // Only show file system paths, and only for temporarily installed add-ons.
+ if (
+ !extension.temporarilyInstalled ||
+ !extension.url ||
+ !extension.url.startsWith("file://")
+ ) {
+ return null;
+ }
+
+ return parseFileUri(extension.url);
+}
+
+function toComponentData(extensions) {
+ return extensions.map(extension => {
+ const type = DEBUG_TARGETS.EXTENSION;
+ const {
+ actor,
+ backgroundScriptStatus,
+ iconDataURL,
+ iconURL,
+ id,
+ manifestURL,
+ name,
+ persistentBackgroundScript,
+ warnings,
+ } = extension;
+ const icon =
+ iconDataURL ||
+ iconURL ||
+ "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+ const location = getFilePath(extension);
+ const uuid = getExtensionUuid(extension);
+ return {
+ name,
+ icon,
+ id,
+ type,
+ details: {
+ actor,
+ backgroundScriptStatus,
+ location,
+ manifestURL,
+ persistentBackgroundScript,
+ uuid,
+ warnings: warnings || [],
+ },
+ };
+ });
+}
+
+module.exports = extensionComponentDataMiddleware;
diff --git a/devtools/client/aboutdebugging/src/middleware/moz.build b/devtools/client/aboutdebugging/src/middleware/moz.build
new file mode 100644
index 0000000000..f50150f569
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/moz.build
@@ -0,0 +1,13 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ "debug-target-listener.js",
+ "error-logging.js",
+ "event-recording.js",
+ "extension-component-data.js",
+ "process-component-data.js",
+ "tab-component-data.js",
+ "worker-component-data.js",
+)
diff --git a/devtools/client/aboutdebugging/src/middleware/process-component-data.js b/devtools/client/aboutdebugging/src/middleware/process-component-data.js
new file mode 100644
index 0000000000..d5cdc6365b
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/process-component-data.js
@@ -0,0 +1,55 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ l10n,
+} = require("resource://devtools/client/aboutdebugging/src/modules/l10n.js");
+
+const {
+ DEBUG_TARGETS,
+ REQUEST_PROCESSES_SUCCESS,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+/**
+ * This middleware converts tabs object that get from DevToolsClient.listProcesses() to
+ * data which is used in DebugTargetItem.
+ */
+const processComponentDataMiddleware = store => next => action => {
+ switch (action.type) {
+ case REQUEST_PROCESSES_SUCCESS: {
+ const mainProcessComponentData = toMainProcessComponentData(
+ action.mainProcess
+ );
+ action.processes = [mainProcessComponentData];
+ break;
+ }
+ }
+
+ return next(action);
+};
+
+function toMainProcessComponentData(process) {
+ const type = DEBUG_TARGETS.PROCESS;
+ const icon = "chrome://devtools/skin/images/aboutdebugging-process-icon.svg";
+
+ // For now, we assume there is only one process and this is the main process
+ // So the name and title are for a remote (multiprocess) browser toolbox.
+ const name = l10n.getString("about-debugging-multiprocess-toolbox-name");
+ const description = l10n.getString(
+ "about-debugging-multiprocess-toolbox-description"
+ );
+
+ return {
+ name,
+ icon,
+ type,
+ details: {
+ description,
+ },
+ };
+}
+
+module.exports = processComponentDataMiddleware;
diff --git a/devtools/client/aboutdebugging/src/middleware/tab-component-data.js b/devtools/client/aboutdebugging/src/middleware/tab-component-data.js
new file mode 100644
index 0000000000..f468926f81
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/tab-component-data.js
@@ -0,0 +1,51 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ DEBUG_TARGETS,
+ REQUEST_TABS_SUCCESS,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+/**
+ * This middleware converts tabs object that get from DevToolsClient.listTabs() to data
+ * which is used in DebugTargetItem.
+ */
+const tabComponentDataMiddleware = store => next => action => {
+ switch (action.type) {
+ case REQUEST_TABS_SUCCESS: {
+ action.tabs = toComponentData(action.tabs);
+ break;
+ }
+ }
+
+ return next(action);
+};
+
+function toComponentData(tabs) {
+ return tabs.map(tab => {
+ const type = DEBUG_TARGETS.TAB;
+ const id = tab.browserId;
+ const icon = tab.favicon
+ ? `data:image/png;base64,${btoa(
+ String.fromCharCode.apply(String, tab.favicon)
+ )}`
+ : "chrome://devtools/skin/images/globe.svg";
+ const name = tab.title || tab.url;
+ const { url, isZombieTab } = tab;
+ return {
+ name,
+ icon,
+ id,
+ type,
+ details: {
+ isZombieTab,
+ url,
+ },
+ };
+ });
+}
+
+module.exports = tabComponentDataMiddleware;
diff --git a/devtools/client/aboutdebugging/src/middleware/worker-component-data.js b/devtools/client/aboutdebugging/src/middleware/worker-component-data.js
new file mode 100644
index 0000000000..178c99e322
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/middleware/worker-component-data.js
@@ -0,0 +1,82 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ DEBUG_TARGETS,
+ REQUEST_WORKERS_SUCCESS,
+ SERVICE_WORKER_FETCH_STATES,
+ SERVICE_WORKER_STATUSES,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+/**
+ * This middleware converts workers object that get from DevToolsClient.listAllWorkers()
+ * to data which is used in DebugTargetItem.
+ */
+const workerComponentDataMiddleware = store => next => action => {
+ switch (action.type) {
+ case REQUEST_WORKERS_SUCCESS: {
+ action.otherWorkers = toComponentData(action.otherWorkers);
+ action.serviceWorkers = toComponentData(action.serviceWorkers, true);
+ action.sharedWorkers = toComponentData(action.sharedWorkers);
+ break;
+ }
+ }
+
+ return next(action);
+};
+
+function getServiceWorkerStatus(worker) {
+ const isActive = worker.state === Ci.nsIServiceWorkerInfo.STATE_ACTIVATED;
+ const isRunning = !!worker.workerDescriptorFront;
+
+ if (isActive && isRunning) {
+ return SERVICE_WORKER_STATUSES.RUNNING;
+ } else if (isActive) {
+ return SERVICE_WORKER_STATUSES.STOPPED;
+ }
+
+ // We cannot get service worker registrations unless the registration is in
+ // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
+ // display a custom state "registering" for now. See Bug 1153292.
+ return SERVICE_WORKER_STATUSES.REGISTERING;
+}
+
+function toComponentData(workers, isServiceWorker) {
+ return workers.map(worker => {
+ // Here `worker` is the worker object created by RootFront.listAllWorkers
+ const type = DEBUG_TARGETS.WORKER;
+ const icon = "chrome://devtools/skin/images/debugging-workers.svg";
+ let { fetch } = worker;
+ const { id, name, registrationFront, scope, subscription } = worker;
+
+ let pushServiceEndpoint = null;
+ let status = null;
+
+ if (isServiceWorker) {
+ fetch = fetch
+ ? SERVICE_WORKER_FETCH_STATES.LISTENING
+ : SERVICE_WORKER_FETCH_STATES.NOT_LISTENING;
+ status = getServiceWorkerStatus(worker);
+ pushServiceEndpoint = subscription ? subscription.endpoint : null;
+ }
+
+ return {
+ details: {
+ fetch,
+ pushServiceEndpoint,
+ registrationFront,
+ scope,
+ status,
+ },
+ icon,
+ id,
+ name,
+ type,
+ };
+ });
+}
+
+module.exports = workerComponentDataMiddleware;