diff options
Diffstat (limited to 'devtools/client/aboutdebugging/src/components/debugtarget')
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", +) |