summaryrefslogtreecommitdiffstats
path: root/devtools/client/aboutdebugging/src/components/debugtarget
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/aboutdebugging/src/components/debugtarget')
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.css97
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.js91
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.css7
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.js80
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.css43
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.js147
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.css27
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.js243
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.css29
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js49
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js58
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/ProcessDetail.js32
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.css26
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.js124
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAdditionalActions.js176
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/TabAction.js52
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/TabDetail.js34
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionAdditionalActions.js182
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionDetail.js67
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.css8
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.js101
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstaller.js52
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/WorkerDetail.js120
-rw-r--r--devtools/client/aboutdebugging/src/components/debugtarget/moz.build22
24 files changed, 1867 insertions, 0 deletions
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.css b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.css
new file mode 100644
index 0000000000..f049f33b23
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.css
@@ -0,0 +1,97 @@
+/* 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/. */
+
+/*
+ * The current layout of debug target item is
+ *
+ * +--------+-----------------------------+----------------+
+ * | | Name | |
+ * | [Icon] |-----------------------------| Action button |
+ * | | Subname | |
+ * +--------+-----------------------------+----------------+
+ * | Detail |
+ * | |
+ * +-------------------------------------------------------+
+ * | Additional actions |
+ * | |
+ * +-------------------------------------------------------+
+ */
+.debug-target-item {
+ display: grid;
+ grid-template-columns: calc(var(--base-unit) * 8) 1fr max-content;
+ grid-template-rows: 1fr minmax(0, auto) auto;
+ grid-column-gap: calc(var(--base-unit) * 2);
+ grid-template-areas: "icon name action"
+ "icon subname action"
+ "detail detail detail"
+ "additional_actions additional_actions additional_actions";
+ margin-block-end: calc(var(--base-unit) * 4);
+
+ padding-block: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
+ padding-inline: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
+}
+
+.debug-target-item__icon {
+ align-self: center;
+ grid-area: icon;
+ margin-inline-start: calc(var(--base-unit) * 3);
+ width: 100%;
+
+ -moz-context-properties: fill;
+ fill: currentColor;
+}
+
+.debug-target-item__name {
+ align-self: center;
+ grid-area: name;
+ font-size: var(--body-20-font-size);
+ font-weight: var(--body-20-font-weight-bold);
+ line-height: 1.5;
+ margin-inline-start: calc(var(--base-unit) * 3);
+}
+
+.debug-target-item__action {
+ grid-area: action;
+ align-self: center;
+ margin-inline-end: calc(var(--base-unit) * 2);
+}
+
+.debug-target-item__additional_actions {
+ grid-area: additional_actions;
+ border-top: 1px solid var(--card-separator-color);
+ margin-block-start: calc(var(--base-unit) * 2);
+ padding-block-start: calc(var(--base-unit) * 2);
+ padding-inline-end: calc(var(--base-unit) * 2);
+}
+
+.debug-target-item__detail {
+ grid-area: detail;
+ margin-block-start: calc(var(--base-unit) * 3);
+}
+
+.debug-target-item__detail--empty {
+ margin-block-start: var(--base-unit);
+}
+
+.debug-target-item__messages {
+ margin-inline: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
+}
+
+.debug-target-item__subname {
+ grid-area: subname;
+ color: var(--secondary-text-color);
+ font-size: var(--caption-20-font-size);
+ font-weight: var(--caption-20-font-weight);
+ line-height: 1.5;
+}
+
+/* The subname is always LTR under the Tabs section,
+ so check its parent's direction to set the correct margin. */
+.debug-target-item:dir(ltr) > .debug-target-item__subname {
+ margin-left: calc(var(--base-unit) * 3);
+}
+
+.debug-target-item:dir(rtl) > .debug-target-item__subname {
+ margin-right: calc(var(--base-unit) * 3);
+}
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.js b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.js
new file mode 100644
index 0000000000..6f19d7be02
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.js
@@ -0,0 +1,91 @@
+/* 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 {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component displays debug target.
+ */
+class DebugTargetItem extends PureComponent {
+ static get propTypes() {
+ return {
+ actionComponent: PropTypes.any.isRequired,
+ additionalActionsComponent: PropTypes.any,
+ detailComponent: PropTypes.any.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ renderAction() {
+ const { actionComponent, dispatch, target } = this.props;
+ return dom.div(
+ {
+ className: "debug-target-item__action",
+ },
+ actionComponent({ dispatch, target })
+ );
+ }
+
+ renderAdditionalActions() {
+ const { additionalActionsComponent, dispatch, target } = this.props;
+
+ if (!additionalActionsComponent) {
+ return null;
+ }
+
+ return dom.section(
+ {
+ className: "debug-target-item__additional_actions",
+ },
+ additionalActionsComponent({ dispatch, target })
+ );
+ }
+
+ renderDetail() {
+ const { detailComponent, target } = this.props;
+ return detailComponent({ target });
+ }
+
+ renderIcon() {
+ return dom.img({
+ className: "debug-target-item__icon qa-debug-target-item-icon",
+ src: this.props.target.icon,
+ });
+ }
+
+ renderName() {
+ return dom.span(
+ {
+ className: "debug-target-item__name ellipsis-text",
+ title: this.props.target.name,
+ },
+ this.props.target.name
+ );
+ }
+
+ render() {
+ return dom.li(
+ {
+ className: "card debug-target-item qa-debug-target-item",
+ "data-qa-target-type": this.props.target.type,
+ },
+ this.renderIcon(),
+ this.renderName(),
+ this.renderAction(),
+ this.renderDetail(),
+ this.renderAdditionalActions()
+ );
+ }
+}
+
+module.exports = DebugTargetItem;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.css b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.css
new file mode 100644
index 0000000000..827983e2bf
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.css
@@ -0,0 +1,7 @@
+/* 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/. */
+
+.debug-target-list {
+ margin-block-start: calc(var(--base-unit) * 4);
+}
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.js b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.js
new file mode 100644
index 0000000000..ce1e7ff12c
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.js
@@ -0,0 +1,80 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const DebugTargetItem = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetItem.js")
+);
+
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component displays list of debug target.
+ */
+class DebugTargetList extends PureComponent {
+ static get propTypes() {
+ return {
+ actionComponent: PropTypes.any.isRequired,
+ additionalActionsComponent: PropTypes.any,
+ detailComponent: PropTypes.any.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ targets: PropTypes.arrayOf(Types.debugTarget).isRequired,
+ };
+ }
+
+ renderEmptyList() {
+ return Localized(
+ {
+ id: "about-debugging-debug-target-list-empty",
+ },
+ dom.p(
+ {
+ className: "qa-debug-target-list-empty",
+ },
+ "Nothing yet."
+ )
+ );
+ }
+
+ render() {
+ const {
+ actionComponent,
+ additionalActionsComponent,
+ detailComponent,
+ dispatch,
+ targets,
+ } = this.props;
+
+ return targets.length === 0
+ ? this.renderEmptyList()
+ : dom.ul(
+ {
+ className: "debug-target-list qa-debug-target-list",
+ },
+ targets.map((target, key) =>
+ DebugTargetItem({
+ actionComponent,
+ additionalActionsComponent,
+ detailComponent,
+ dispatch,
+ key,
+ target,
+ })
+ )
+ );
+ }
+}
+
+module.exports = DebugTargetList;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.css b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.css
new file mode 100644
index 0000000000..616b4ac28c
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.css
@@ -0,0 +1,43 @@
+/* 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/. */
+
+/*
+ * Style for the heading of a debug target pane
+ * +-----------------+---------------+-----------------+
+ * | [Category icon] | Category name | [Collapse icon] |
+ * +-----------------+---------------+-----------------+
+ */
+.debug-target-pane__heading {
+ grid-template-columns: var(--main-subheading-icon-size) max-content calc(var(--base-unit) * 3);
+ user-select: none;
+}
+
+.debug-target-pane__icon {
+ transition: transform 150ms cubic-bezier(.07, .95, 0, 1);
+ transform: rotate(90deg);
+}
+
+.debug-target-pane__icon--collapsed {
+ transform: rotate(0deg);
+}
+
+.debug-target-pane__icon--collapsed:dir(rtl) {
+ transform: rotate(180deg);
+}
+
+.debug-target-pane__title {
+ cursor: pointer;
+}
+
+.debug-target-pane__collapsable {
+ overflow: hidden;
+ /* padding will give space for card shadow to appear and
+ margin will correct the alignment */
+ margin-inline: calc(var(--card-shadow-blur-radius) * -1);
+ padding-inline: var(--card-shadow-blur-radius);
+}
+
+.debug-target-pane__collapsable--collapsed {
+ max-height: 0;
+}
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.js b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.js
new file mode 100644
index 0000000000..abfa1042b8
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetPane.js
@@ -0,0 +1,147 @@
+/* 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 {
+ createFactory,
+ createRef,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+
+const DebugTargetList = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/DebugTargetList.js")
+);
+
+const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component provides list for debug target and name area.
+ */
+class DebugTargetPane extends PureComponent {
+ static get propTypes() {
+ return {
+ actionComponent: PropTypes.any.isRequired,
+ additionalActionsComponent: PropTypes.any,
+ children: PropTypes.node,
+ collapsibilityKey: PropTypes.string.isRequired,
+ detailComponent: PropTypes.any.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ // Provided by wrapping the component with FluentReact.withLocalization.
+ getString: PropTypes.func.isRequired,
+ icon: PropTypes.string.isRequired,
+ isCollapsed: PropTypes.bool.isRequired,
+ name: PropTypes.string.isRequired,
+ targets: PropTypes.arrayOf(Types.debugTarget).isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+ this.collapsableRef = createRef();
+ }
+
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ if (snapshot === null) {
+ return;
+ }
+
+ const el = this.collapsableRef.current;
+
+ // Cancel existing animation which is collapsing/expanding.
+ for (const animation of el.getAnimations()) {
+ animation.cancel();
+ }
+
+ el.animate(
+ { maxHeight: [`${snapshot}px`, `${el.clientHeight}px`] },
+ { duration: 150, easing: "cubic-bezier(.07, .95, 0, 1)" }
+ );
+ }
+
+ getSnapshotBeforeUpdate(prevProps) {
+ if (this.props.isCollapsed !== prevProps.isCollapsed) {
+ return this.collapsableRef.current.clientHeight;
+ }
+
+ return null;
+ }
+
+ toggleCollapsibility() {
+ const { collapsibilityKey, dispatch, isCollapsed } = this.props;
+ dispatch(
+ Actions.updateDebugTargetCollapsibility(collapsibilityKey, !isCollapsed)
+ );
+ }
+
+ render() {
+ const {
+ actionComponent,
+ additionalActionsComponent,
+ children,
+ detailComponent,
+ dispatch,
+ getString,
+ icon,
+ isCollapsed,
+ name,
+ targets,
+ } = this.props;
+
+ const title = getString("about-debugging-collapse-expand-debug-targets");
+
+ return dom.section(
+ {
+ className: "qa-debug-target-pane",
+ },
+ dom.a(
+ {
+ className:
+ "undecorated-link debug-target-pane__title " +
+ "qa-debug-target-pane-title",
+ title,
+ onClick: e => this.toggleCollapsibility(),
+ },
+ dom.h2(
+ { className: "main-subheading debug-target-pane__heading" },
+ dom.img({
+ className: "main-subheading__icon",
+ src: icon,
+ }),
+ `${name} (${targets.length})`,
+ dom.img({
+ className:
+ "main-subheading__icon debug-target-pane__icon" +
+ (isCollapsed ? " debug-target-pane__icon--collapsed" : ""),
+ src: "chrome://devtools/skin/images/arrow-e.svg",
+ })
+ )
+ ),
+ dom.div(
+ {
+ className:
+ "debug-target-pane__collapsable qa-debug-target-pane__collapsable" +
+ (isCollapsed ? " debug-target-pane__collapsable--collapsed" : ""),
+ ref: this.collapsableRef,
+ },
+ children,
+ DebugTargetList({
+ actionComponent,
+ additionalActionsComponent,
+ detailComponent,
+ dispatch,
+ isCollapsed,
+ targets,
+ })
+ )
+ );
+ }
+}
+
+module.exports = FluentReact.withLocalization(DebugTargetPane);
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.css b/devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.css
new file mode 100644
index 0000000000..6a4befa76a
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.css
@@ -0,0 +1,27 @@
+/* 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/. */
+
+.extension-backgroundscript {
+ display: flex;
+ column-gap: calc(var(--base-unit) * 2);
+}
+
+.extension-backgroundscript__status {
+ display: flex;
+ align-items: center;
+ float: inline-end;
+}
+
+.extension-backgroundscript__status::before {
+ background-color: var(--grey-50);
+ border-radius: 100%;
+ content: "";
+ height: calc(var(--base-unit) * 2);
+ margin-inline-end: var(--base-unit);
+ width: calc(var(--base-unit) * 2);
+}
+
+.extension-backgroundscript__status--running::before {
+ background-color: var(--success-background);
+}
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.js b/devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.js
new file mode 100644
index 0000000000..ef14483723
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.js
@@ -0,0 +1,243 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const {
+ getCurrentRuntimeDetails,
+} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js");
+
+const DetailsLog = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/shared/DetailsLog.js")
+);
+const FieldPair = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js")
+);
+const Message = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/shared/Message.js")
+);
+
+const {
+ EXTENSION_BGSCRIPT_STATUSES,
+ MESSAGE_LEVEL,
+ RUNTIMES,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component displays detail information for extension.
+ */
+class ExtensionDetail extends PureComponent {
+ static get propTypes() {
+ return {
+ children: PropTypes.node,
+ // Provided by wrapping the component with FluentReact.withLocalization.
+ getString: PropTypes.func.isRequired,
+ // Provided by redux state
+ runtimeDetails: Types.runtimeDetails.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ renderWarnings() {
+ const { warnings } = this.props.target.details;
+
+ if (!warnings.length) {
+ return null;
+ }
+
+ return dom.section(
+ {
+ className: "debug-target-item__messages",
+ },
+ warnings.map((warning, index) => {
+ return Message(
+ {
+ level: MESSAGE_LEVEL.WARNING,
+ isCloseable: true,
+ key: `warning-${index}`,
+ },
+ DetailsLog(
+ {
+ type: MESSAGE_LEVEL.WARNING,
+ },
+ dom.p(
+ {
+ className: "technical-text",
+ },
+ warning
+ )
+ )
+ );
+ })
+ );
+ }
+
+ renderUUID() {
+ const { uuid } = this.props.target.details;
+ if (!uuid) {
+ return null;
+ }
+
+ return Localized(
+ {
+ id: "about-debugging-extension-uuid",
+ attrs: { label: true },
+ },
+ FieldPair({
+ label: "Internal UUID",
+ value: uuid,
+ })
+ );
+ }
+
+ renderExtensionId() {
+ const { id } = this.props.target;
+
+ return Localized(
+ {
+ id: "about-debugging-extension-id",
+ attrs: { label: true },
+ },
+ FieldPair({
+ label: "Extension ID",
+ value: id,
+ })
+ );
+ }
+
+ renderLocation() {
+ const { location } = this.props.target.details;
+ if (!location) {
+ return null;
+ }
+
+ return Localized(
+ {
+ id: "about-debugging-extension-location",
+ attrs: { label: true },
+ },
+ FieldPair({
+ label: "Location",
+ value: location,
+ })
+ );
+ }
+
+ renderManifest() {
+ // Manifest links are only relevant when debugging the current Firefox
+ // instance.
+ if (this.props.runtimeDetails.info.type !== RUNTIMES.THIS_FIREFOX) {
+ return null;
+ }
+
+ const { manifestURL } = this.props.target.details;
+ const link = dom.a(
+ {
+ className: "qa-manifest-url",
+ href: manifestURL,
+ target: "_blank",
+ },
+ manifestURL
+ );
+
+ return Localized(
+ {
+ id: "about-debugging-extension-manifest-url",
+ attrs: { label: true },
+ },
+ FieldPair({
+ label: "Manifest URL",
+ value: link,
+ })
+ );
+ }
+
+ renderBackgroundScriptStatus() {
+ // The status of the background script is only relevant if it is
+ // not persistent.
+ const { persistentBackgroundScript } = this.props.target.details;
+ if (!(persistentBackgroundScript === false)) {
+ return null;
+ }
+
+ const { backgroundScriptStatus } = this.props.target.details;
+
+ let status;
+ let statusLocalizationId;
+ let statusClassName;
+
+ if (backgroundScriptStatus === EXTENSION_BGSCRIPT_STATUSES.RUNNING) {
+ status = `extension-backgroundscript__status--running`;
+ statusLocalizationId = `about-debugging-extension-backgroundscript-status-running`;
+ statusClassName = `extension-backgroundscript__status--running`;
+ } else {
+ status = `extension-backgroundscript__status--stopped`;
+ statusLocalizationId = `about-debugging-extension-backgroundscript-status-stopped`;
+ statusClassName = `extension-backgroundscript__status--stopped`;
+ }
+
+ return Localized(
+ {
+ id: "about-debugging-extension-backgroundscript",
+ attrs: { label: true },
+ },
+ FieldPair({
+ label: "Background Script",
+ value: Localized(
+ {
+ id: statusLocalizationId,
+ },
+ dom.span(
+ {
+ className: `extension-backgroundscript__status qa-extension-backgroundscript-status ${statusClassName}`,
+ },
+ status
+ )
+ ),
+ })
+ );
+ }
+
+ render() {
+ return dom.section(
+ {
+ className: "debug-target-item__detail",
+ },
+ this.renderWarnings(),
+ dom.dl(
+ {},
+ this.renderLocation(),
+ this.renderExtensionId(),
+ this.renderUUID(),
+ this.renderManifest(),
+ this.renderBackgroundScriptStatus(),
+ this.props.children
+ )
+ );
+ }
+}
+
+const mapStateToProps = state => {
+ return {
+ runtimeDetails: getCurrentRuntimeDetails(state.runtimes),
+ };
+};
+
+module.exports = FluentReact.withLocalization(
+ connect(mapStateToProps)(ExtensionDetail)
+);
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.css b/devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.css
new file mode 100644
index 0000000000..a0b290d2a3
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.css
@@ -0,0 +1,29 @@
+/* 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/. */
+
+.fieldpair {
+ display: grid;
+ grid-template-columns: auto auto;
+ border-top: 1px solid var(--card-separator-color);
+ padding-block: calc(var(--base-unit) * 2);
+ padding-inline: calc(var(--base-unit) * 4) calc(var(--base-unit) * 2);
+}
+
+.fieldpair:last-child {
+ padding-block-end: 0;
+}
+
+.fieldpair__title {
+ margin-inline-end: var(--base-unit);
+ font-size: var(--caption-20-font-size);
+ font-weight: var(--caption-20-font-weight);
+}
+
+.fieldpair__description {
+ color: var(--fieldpair-text-color);
+ flex: 1;
+ font-size: var(--caption-20-font-size);
+ font-weight: var(--caption-20-font-weight);
+ text-align: end;
+}
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js b/devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js
new file mode 100644
index 0000000000..5e7ae53c65
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js
@@ -0,0 +1,49 @@
+/* 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 {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+/* Renders a pair of `<dt>` (label) + `<dd>` (value) field. */
+class FieldPair extends PureComponent {
+ static get propTypes() {
+ return {
+ className: PropTypes.string,
+ label: PropTypes.node.isRequired,
+ value: PropTypes.node,
+ };
+ }
+
+ render() {
+ const { label, value } = this.props;
+ return dom.div(
+ {
+ className: "fieldpair",
+ },
+ dom.dt(
+ {
+ className:
+ "fieldpair__title " +
+ (this.props.className ? this.props.className : ""),
+ },
+ label
+ ),
+ value
+ ? dom.dd(
+ {
+ className: "fieldpair__description ellipsis-text",
+ },
+ value
+ )
+ : null
+ );
+ }
+}
+
+module.exports = FieldPair;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js b/devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js
new file mode 100644
index 0000000000..f7aff438a4
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js
@@ -0,0 +1,58 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component provides inspect button.
+ */
+class InspectAction extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ disabled: PropTypes.bool,
+ target: Types.debugTarget.isRequired,
+ title: PropTypes.string,
+ };
+ }
+
+ inspect() {
+ const { dispatch, target } = this.props;
+ dispatch(Actions.inspectDebugTarget(target.type, target.id));
+ }
+
+ render() {
+ const { disabled, title } = this.props;
+
+ return Localized(
+ {
+ id: "about-debugging-debug-target-inspect-button",
+ },
+ dom.button(
+ {
+ onClick: e => this.inspect(),
+ className: "default-button qa-debug-target-inspect-button",
+ disabled,
+ title,
+ },
+ "Inspect"
+ )
+ );
+ }
+}
+
+module.exports = InspectAction;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/ProcessDetail.js b/devtools/client/aboutdebugging/src/components/debugtarget/ProcessDetail.js
new file mode 100644
index 0000000000..a9973e90e3
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/ProcessDetail.js
@@ -0,0 +1,32 @@
+/* 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 {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component displays detail information for a process.
+ */
+class ProcessDetail extends PureComponent {
+ static get propTypes() {
+ return {
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ render() {
+ const { description } = this.props.target.details;
+ return dom.p(
+ { className: "debug-target-item__subname ellipsis-text" },
+ description
+ );
+ }
+}
+
+module.exports = ProcessDetail;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.css b/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.css
new file mode 100644
index 0000000000..c3ac2dc872
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.css
@@ -0,0 +1,26 @@
+/* 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/. */
+
+.service-worker-action {
+ display: flex;
+ column-gap: calc(var(--base-unit) * 2);
+}
+
+.service-worker-action__status {
+ display: flex;
+ align-items: center;
+}
+
+.service-worker-action__status::before {
+ background-color: var(--grey-50);
+ border-radius: 100%;
+ content: "";
+ height: calc(var(--base-unit) * 2);
+ margin-inline-end: var(--base-unit);
+ width: calc(var(--base-unit) * 2);
+}
+
+.service-worker-action__status--running::before {
+ background-color: var(--success-background);
+}
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.js b/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.js
new file mode 100644
index 0000000000..38aede94f4
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAction.js
@@ -0,0 +1,124 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const {
+ getCurrentRuntimeDetails,
+} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js");
+
+const InspectAction = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js")
+);
+
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+const {
+ SERVICE_WORKER_STATUSES,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+/**
+ * This component displays buttons for service worker.
+ */
+class ServiceWorkerAction extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ // Provided by redux state
+ runtimeDetails: Types.runtimeDetails.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ _renderInspectAction() {
+ const { status } = this.props.target.details;
+ const shallRenderInspectAction =
+ status === SERVICE_WORKER_STATUSES.RUNNING ||
+ status === SERVICE_WORKER_STATUSES.REGISTERING;
+
+ if (!shallRenderInspectAction) {
+ return null;
+ }
+
+ const { canDebugServiceWorkers } = this.props.runtimeDetails;
+ return Localized(
+ {
+ id: "about-debugging-worker-inspect-action-disabled",
+ attrs: {
+ // Show an explanatory title only if the action is disabled.
+ title: !canDebugServiceWorkers,
+ },
+ },
+ InspectAction({
+ disabled: !canDebugServiceWorkers,
+ dispatch: this.props.dispatch,
+ target: this.props.target,
+ })
+ );
+ }
+
+ _getStatusLocalizationId(status) {
+ switch (status) {
+ case SERVICE_WORKER_STATUSES.REGISTERING.toLowerCase():
+ return "about-debugging-worker-status-registering";
+ case SERVICE_WORKER_STATUSES.RUNNING.toLowerCase():
+ return "about-debugging-worker-status-running";
+ case SERVICE_WORKER_STATUSES.STOPPED.toLowerCase():
+ return "about-debugging-worker-status-stopped";
+ default:
+ // Assume status is stopped for unknown status value.
+ return "about-debugging-worker-status-stopped";
+ }
+ }
+
+ _renderStatus() {
+ const status = this.props.target.details.status.toLowerCase();
+ const statusClassName =
+ status === SERVICE_WORKER_STATUSES.RUNNING.toLowerCase()
+ ? "service-worker-action__status--running"
+ : "";
+
+ return Localized(
+ {
+ id: this._getStatusLocalizationId(status),
+ },
+ dom.span(
+ {
+ className: `service-worker-action__status qa-worker-status ${statusClassName}`,
+ },
+ status
+ )
+ );
+ }
+
+ render() {
+ return dom.div(
+ {
+ className: "service-worker-action",
+ },
+ this._renderStatus(),
+ this._renderInspectAction()
+ );
+ }
+}
+
+const mapStateToProps = state => {
+ return {
+ runtimeDetails: getCurrentRuntimeDetails(state.runtimes),
+ };
+};
+
+module.exports = connect(mapStateToProps)(ServiceWorkerAction);
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAdditionalActions.js b/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAdditionalActions.js
new file mode 100644
index 0000000000..38262ad511
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/ServiceWorkerAdditionalActions.js
@@ -0,0 +1,176 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const {
+ getCurrentRuntimeDetails,
+} = require("resource://devtools/client/aboutdebugging/src/modules/runtimes-state-helper.js");
+
+const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+const {
+ SERVICE_WORKER_STATUSES,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+/**
+ * The main purpose of this component is to expose a meaningful prop
+ * disabledTitle that can be used with fluent localization.
+ */
+class _ActionButton extends PureComponent {
+ static get propTypes() {
+ return {
+ children: PropTypes.node,
+ className: PropTypes.string.isRequired,
+ disabled: PropTypes.bool,
+ disabledTitle: PropTypes.string,
+ onClick: PropTypes.func.isRequired,
+ };
+ }
+
+ render() {
+ const { className, disabled, disabledTitle, onClick } = this.props;
+ return dom.button(
+ {
+ className,
+ disabled,
+ onClick: e => onClick(),
+ title: disabled && disabledTitle ? disabledTitle : undefined,
+ },
+ this.props.children
+ );
+ }
+}
+const ActionButtonFactory = createFactory(_ActionButton);
+
+/**
+ * This component displays buttons for service worker.
+ */
+class ServiceWorkerAdditionalActions extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ // Provided by wrapping the component with FluentReact.withLocalization.
+ getString: PropTypes.func.isRequired,
+ // Provided by redux state
+ runtimeDetails: Types.runtimeDetails.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ push() {
+ const { dispatch, target } = this.props;
+ dispatch(
+ Actions.pushServiceWorker(target.id, target.details.registrationFront)
+ );
+ }
+
+ start() {
+ const { dispatch, target } = this.props;
+ dispatch(Actions.startServiceWorker(target.details.registrationFront));
+ }
+
+ unregister() {
+ const { dispatch, target } = this.props;
+ dispatch(Actions.unregisterServiceWorker(target.details.registrationFront));
+ }
+
+ _renderButton({ className, disabled, key, labelId, onClick }) {
+ return Localized(
+ {
+ id: labelId,
+ attrs: {
+ disabledTitle: !!disabled,
+ },
+ key,
+ },
+ ActionButtonFactory(
+ {
+ className,
+ disabled,
+ onClick: e => onClick(),
+ },
+ labelId
+ )
+ );
+ }
+
+ _renderPushButton() {
+ return this._renderButton({
+ className: "default-button default-button--micro qa-push-button",
+ disabled: !this.props.runtimeDetails.canDebugServiceWorkers,
+ key: "service-worker-push-button",
+ labelId: "about-debugging-worker-action-push2",
+ onClick: this.push.bind(this),
+ });
+ }
+
+ _renderStartButton() {
+ return this._renderButton({
+ className: "default-button default-button--micro qa-start-button",
+ disabled: !this.props.runtimeDetails.canDebugServiceWorkers,
+ key: "service-worker-start-button",
+ labelId: "about-debugging-worker-action-start2",
+ onClick: this.start.bind(this),
+ });
+ }
+
+ _renderUnregisterButton() {
+ return this._renderButton({
+ className: "default-button default-button--micro qa-unregister-button",
+ key: "service-worker-unregister-button",
+ labelId: "about-debugging-worker-action-unregister",
+ disabled: false,
+ onClick: this.unregister.bind(this),
+ });
+ }
+
+ _renderActionButtons() {
+ const { status } = this.props.target.details;
+
+ switch (status) {
+ case SERVICE_WORKER_STATUSES.RUNNING:
+ return [this._renderUnregisterButton(), this._renderPushButton()];
+ case SERVICE_WORKER_STATUSES.REGISTERING:
+ return null;
+ case SERVICE_WORKER_STATUSES.STOPPED:
+ return [this._renderUnregisterButton(), this._renderStartButton()];
+ default:
+ console.error("Unexpected service worker status: " + status);
+ return null;
+ }
+ }
+
+ render() {
+ return dom.div(
+ {
+ className: "toolbar toolbar--right-align",
+ },
+ this._renderActionButtons()
+ );
+ }
+}
+
+const mapStateToProps = state => {
+ return {
+ runtimeDetails: getCurrentRuntimeDetails(state.runtimes),
+ };
+};
+
+module.exports = FluentReact.withLocalization(
+ connect(mapStateToProps)(ServiceWorkerAdditionalActions)
+);
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/TabAction.js b/devtools/client/aboutdebugging/src/components/debugtarget/TabAction.js
new file mode 100644
index 0000000000..132f1e5f44
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/TabAction.js
@@ -0,0 +1,52 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const InspectAction = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/InspectAction.js")
+);
+
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component displays the inspect button for tabs.
+ */
+class TabAction extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ render() {
+ const isZombieTab = this.props.target.details.isZombieTab;
+ return Localized(
+ {
+ id: "about-debugging-zombie-tab-inspect-action-disabled",
+ attrs: {
+ // Show an explanatory title only if the action is disabled.
+ title: isZombieTab,
+ },
+ },
+ InspectAction({
+ disabled: isZombieTab,
+ dispatch: this.props.dispatch,
+ target: this.props.target,
+ })
+ );
+ }
+}
+
+module.exports = FluentReact.withLocalization(TabAction);
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/TabDetail.js b/devtools/client/aboutdebugging/src/components/debugtarget/TabDetail.js
new file mode 100644
index 0000000000..dcbd3f0a4d
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/TabDetail.js
@@ -0,0 +1,34 @@
+/* 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 {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component displays detail information for tab.
+ */
+class TabDetail extends PureComponent {
+ static get propTypes() {
+ return {
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ render() {
+ return dom.div(
+ {
+ className: "debug-target-item__subname ellipsis-text",
+ dir: "ltr",
+ },
+ this.props.target.details.url
+ );
+ }
+}
+
+module.exports = TabDetail;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionAdditionalActions.js b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionAdditionalActions.js
new file mode 100644
index 0000000000..44b7d3e167
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionAdditionalActions.js
@@ -0,0 +1,182 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+const DetailsLog = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/shared/DetailsLog.js")
+);
+const Message = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/shared/Message.js")
+);
+const {
+ MESSAGE_LEVEL,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+/**
+ * This component provides components that reload/remove temporary extension.
+ */
+class TemporaryExtensionAdditionalActions extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ reload() {
+ const { dispatch, target } = this.props;
+ dispatch(Actions.reloadTemporaryExtension(target.id));
+ }
+
+ terminateBackgroundScript() {
+ const { dispatch, target } = this.props;
+ dispatch(Actions.terminateExtensionBackgroundScript(target.id));
+ }
+
+ remove() {
+ const { dispatch, target } = this.props;
+ dispatch(Actions.removeTemporaryExtension(target.id));
+ }
+
+ renderReloadError() {
+ const { reloadError } = this.props.target.details;
+
+ if (!reloadError) {
+ return null;
+ }
+
+ return Message(
+ {
+ className: "qa-temporary-extension-reload-error",
+ level: MESSAGE_LEVEL.ERROR,
+ key: "reload-error",
+ },
+ DetailsLog(
+ {
+ type: MESSAGE_LEVEL.ERROR,
+ },
+ dom.p(
+ {
+ className: "technical-text",
+ },
+ reloadError
+ )
+ )
+ );
+ }
+
+ renderTerminateBackgroundScriptError() {
+ const { lastTerminateBackgroundScriptError } = this.props.target.details;
+
+ if (!lastTerminateBackgroundScriptError) {
+ return null;
+ }
+
+ return Message(
+ {
+ className: "qa-temporary-extension-terminate-backgroundscript-error",
+ level: MESSAGE_LEVEL.ERROR,
+ key: "terminate-backgroundscript-error",
+ },
+ DetailsLog(
+ {
+ type: MESSAGE_LEVEL.ERROR,
+ },
+ dom.p(
+ {
+ className: "technical-text",
+ },
+ lastTerminateBackgroundScriptError
+ )
+ )
+ );
+ }
+
+ renderTerminateBackgroundScriptButton() {
+ const { persistentBackgroundScript } = this.props.target.details;
+
+ // For extensions with a non persistent background script
+ // also include a "terminate background script" action.
+ if (persistentBackgroundScript !== false) {
+ return null;
+ }
+
+ return Localized(
+ {
+ id: "about-debugging-tmp-extension-terminate-bgscript-button",
+ },
+ dom.button(
+ {
+ className:
+ "default-button default-button--micro " +
+ "qa-temporary-extension-terminate-bgscript-button",
+ onClick: e => this.terminateBackgroundScript(),
+ },
+ "Terminate Background Script"
+ )
+ );
+ }
+
+ renderRemoveButton() {
+ return Localized(
+ {
+ id: "about-debugging-tmp-extension-remove-button",
+ },
+ dom.button(
+ {
+ className:
+ "default-button default-button--micro " +
+ "qa-temporary-extension-remove-button",
+ onClick: e => this.remove(),
+ },
+ "Remove"
+ )
+ );
+ }
+
+ render() {
+ return [
+ dom.div(
+ {
+ className: "toolbar toolbar--right-align",
+ key: "actions",
+ },
+ this.renderTerminateBackgroundScriptButton(),
+ Localized(
+ {
+ id: "about-debugging-tmp-extension-reload-button",
+ },
+ dom.button(
+ {
+ className:
+ "default-button default-button--micro " +
+ "qa-temporary-extension-reload-button",
+ onClick: e => this.reload(),
+ },
+ "Reload"
+ )
+ ),
+ this.renderRemoveButton()
+ ),
+ this.renderReloadError(),
+ this.renderTerminateBackgroundScriptError(),
+ ];
+ }
+}
+
+module.exports = TemporaryExtensionAdditionalActions;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionDetail.js b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionDetail.js
new file mode 100644
index 0000000000..6c589472d6
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionDetail.js
@@ -0,0 +1,67 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const ExtensionDetail = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/ExtensionDetail.js")
+);
+const FieldPair = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js")
+);
+
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+const TEMP_ID_DOC_URL =
+ "https://developer.mozilla.org/Add-ons/WebExtensions/WebExtensions_and_the_Add-on_ID";
+
+/**
+ * This component displays detail information for a temporary extension.
+ */
+class TemporaryExtensionDetail extends PureComponent {
+ static get propTypes() {
+ return {
+ // Provided by wrapping the component with FluentReact.withLocalization.
+ getString: PropTypes.func.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ renderTemporaryIdMessage() {
+ return Localized(
+ {
+ id: "about-debugging-tmp-extension-temporary-id",
+ a: dom.a({
+ className: "qa-temporary-id-link",
+ href: TEMP_ID_DOC_URL,
+ target: "_blank",
+ }),
+ },
+ dom.div({
+ className: "qa-temporary-id-message",
+ })
+ );
+ }
+
+ render() {
+ return ExtensionDetail(
+ {
+ target: this.props.target,
+ },
+ FieldPair({ label: this.renderTemporaryIdMessage() })
+ );
+ }
+}
+
+module.exports = FluentReact.withLocalization(TemporaryExtensionDetail);
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.css b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.css
new file mode 100644
index 0000000000..9166a3b615
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.css
@@ -0,0 +1,8 @@
+/* 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/. */
+
+.temporary-extension-install-section__toolbar {
+ display: flex;
+ justify-content: end;
+}
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.js b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.js
new file mode 100644
index 0000000000..f85618f73d
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstallSection.js
@@ -0,0 +1,101 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const DetailsLog = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/shared/DetailsLog.js")
+);
+const Message = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/shared/Message.js")
+);
+const TemporaryExtensionInstaller = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstaller.js")
+);
+
+const {
+ MESSAGE_LEVEL,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+/**
+ * This component provides an installer and error message area for temporary extension.
+ */
+class TemporaryExtensionInstallSection extends PureComponent {
+ static get propTypes() {
+ return {
+ dispatch: PropTypes.func.isRequired,
+ temporaryInstallError: PropTypes.object,
+ };
+ }
+
+ renderError() {
+ const { temporaryInstallError } = this.props;
+
+ if (!temporaryInstallError) {
+ return null;
+ }
+
+ const errorMessages = [
+ temporaryInstallError.message,
+ ...(temporaryInstallError.additionalErrors || []),
+ ];
+
+ const errors = errorMessages.map((message, index) => {
+ return dom.p(
+ {
+ className: "technical-text",
+ key: "tmp-extension-install-error-" + index,
+ },
+ message
+ );
+ });
+
+ return Message(
+ {
+ level: MESSAGE_LEVEL.ERROR,
+ className: "qa-tmp-extension-install-error",
+ isCloseable: true,
+ },
+ Localized(
+ {
+ id: "about-debugging-tmp-extension-install-error",
+ },
+ dom.p({}, "about-debugging-tmp-extension-install-error")
+ ),
+ DetailsLog(
+ {
+ type: MESSAGE_LEVEL.ERROR,
+ },
+ errors
+ )
+ );
+ }
+
+ render() {
+ const { dispatch } = this.props;
+
+ return dom.section(
+ {},
+ dom.div(
+ {
+ className: "temporary-extension-install-section__toolbar",
+ },
+ TemporaryExtensionInstaller({ dispatch })
+ ),
+ this.renderError()
+ );
+ }
+}
+
+module.exports = TemporaryExtensionInstallSection;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstaller.js b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstaller.js
new file mode 100644
index 0000000000..e515c647ec
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/TemporaryExtensionInstaller.js
@@ -0,0 +1,52 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const Actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
+
+/**
+ * This component provides an installer for temporary extension.
+ */
+class TemporaryExtensionInstaller extends PureComponent {
+ static get propTypes() {
+ return {
+ className: PropTypes.string,
+ dispatch: PropTypes.func.isRequired,
+ };
+ }
+
+ install() {
+ this.props.dispatch(Actions.installTemporaryExtension());
+ }
+
+ render() {
+ const { className } = this.props;
+
+ return Localized(
+ {
+ id: "about-debugging-tmp-extension-install-button",
+ },
+ dom.button(
+ {
+ className: `${className} default-button qa-temporary-extension-install-button`,
+ onClick: e => this.install(),
+ },
+ "Load Temporary Add-on…"
+ )
+ );
+ }
+}
+
+module.exports = TemporaryExtensionInstaller;
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/WorkerDetail.js b/devtools/client/aboutdebugging/src/components/debugtarget/WorkerDetail.js
new file mode 100644
index 0000000000..b69252c430
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/WorkerDetail.js
@@ -0,0 +1,120 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const {
+ SERVICE_WORKER_FETCH_STATES,
+} = require("resource://devtools/client/aboutdebugging/src/constants.js");
+
+const FieldPair = createFactory(
+ require("resource://devtools/client/aboutdebugging/src/components/debugtarget/FieldPair.js")
+);
+
+const Types = require("resource://devtools/client/aboutdebugging/src/types/index.js");
+
+/**
+ * This component displays detail information for worker.
+ */
+class WorkerDetail extends PureComponent {
+ static get propTypes() {
+ return {
+ // Provided by wrapping the component with FluentReact.withLocalization.
+ getString: PropTypes.func.isRequired,
+ target: Types.debugTarget.isRequired,
+ };
+ }
+
+ renderFetch() {
+ const { fetch } = this.props.target.details;
+ const isListening = fetch === SERVICE_WORKER_FETCH_STATES.LISTENING;
+ const localizationId = isListening
+ ? "about-debugging-worker-fetch-listening"
+ : "about-debugging-worker-fetch-not-listening";
+
+ return Localized(
+ {
+ id: localizationId,
+ attrs: {
+ label: true,
+ value: true,
+ },
+ },
+ FieldPair({
+ className: isListening
+ ? "qa-worker-fetch-listening"
+ : "qa-worker-fetch-not-listening",
+ label: "Fetch",
+ slug: "fetch",
+ value: "about-debugging-worker-fetch-value",
+ })
+ );
+ }
+
+ renderPushService() {
+ const { pushServiceEndpoint } = this.props.target.details;
+
+ return Localized(
+ {
+ id: "about-debugging-worker-push-service",
+ attrs: { label: true },
+ },
+ FieldPair({
+ slug: "push-service",
+ label: "Push Service",
+ value: dom.span(
+ {
+ className: "qa-worker-push-service-value",
+ },
+ pushServiceEndpoint
+ ),
+ })
+ );
+ }
+
+ renderScope() {
+ const { scope } = this.props.target.details;
+
+ return Localized(
+ {
+ id: "about-debugging-worker-scope",
+ attrs: { label: true },
+ },
+ FieldPair({
+ slug: "scope",
+ label: "Scope",
+ value: scope,
+ })
+ );
+ }
+
+ render() {
+ const { fetch, pushServiceEndpoint, scope } = this.props.target.details;
+
+ const isEmptyList = !pushServiceEndpoint && !fetch && !scope && !status;
+
+ return dom.dl(
+ {
+ className:
+ "debug-target-item__detail" +
+ (isEmptyList ? " debug-target-item__detail--empty" : ""),
+ },
+ pushServiceEndpoint ? this.renderPushService() : null,
+ fetch ? this.renderFetch() : null,
+ scope ? this.renderScope() : null
+ );
+ }
+}
+
+module.exports = FluentReact.withLocalization(WorkerDetail);
diff --git a/devtools/client/aboutdebugging/src/components/debugtarget/moz.build b/devtools/client/aboutdebugging/src/components/debugtarget/moz.build
new file mode 100644
index 0000000000..981e6887a2
--- /dev/null
+++ b/devtools/client/aboutdebugging/src/components/debugtarget/moz.build
@@ -0,0 +1,22 @@
+# 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(
+ "DebugTargetItem.js",
+ "DebugTargetList.js",
+ "DebugTargetPane.js",
+ "ExtensionDetail.js",
+ "FieldPair.js",
+ "InspectAction.js",
+ "ProcessDetail.js",
+ "ServiceWorkerAction.js",
+ "ServiceWorkerAdditionalActions.js",
+ "TabAction.js",
+ "TabDetail.js",
+ "TemporaryExtensionAdditionalActions.js",
+ "TemporaryExtensionDetail.js",
+ "TemporaryExtensionInstaller.js",
+ "TemporaryExtensionInstallSection.js",
+ "WorkerDetail.js",
+)