summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/components/DebugTargetInfo.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/components/DebugTargetInfo.js')
-rw-r--r--devtools/client/framework/components/DebugTargetInfo.js401
1 files changed, 401 insertions, 0 deletions
diff --git a/devtools/client/framework/components/DebugTargetInfo.js b/devtools/client/framework/components/DebugTargetInfo.js
new file mode 100644
index 0000000000..e3911e96c9
--- /dev/null
+++ b/devtools/client/framework/components/DebugTargetInfo.js
@@ -0,0 +1,401 @@
+/* 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,
+ createFactory,
+} = 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 {
+ CONNECTION_TYPES,
+} = require("resource://devtools/client/shared/remote-debugging/constants.js");
+const DESCRIPTOR_TYPES = require("resource://devtools/client/fronts/descriptors/descriptor-types.js");
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+/**
+ * This is header that should be displayed on top of the toolbox when using
+ * about:devtools-toolbox.
+ */
+class DebugTargetInfo extends PureComponent {
+ static get propTypes() {
+ return {
+ alwaysOnTop: PropTypes.boolean.isRequired,
+ focusedState: PropTypes.boolean,
+ toggleAlwaysOnTop: PropTypes.func.isRequired,
+ debugTargetData: PropTypes.shape({
+ connectionType: PropTypes.oneOf(Object.values(CONNECTION_TYPES))
+ .isRequired,
+ runtimeInfo: PropTypes.shape({
+ deviceName: PropTypes.string,
+ icon: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ version: PropTypes.string.isRequired,
+ }).isRequired,
+ descriptorType: PropTypes.oneOf(Object.values(DESCRIPTOR_TYPES))
+ .isRequired,
+ }).isRequired,
+ L10N: PropTypes.object.isRequired,
+ toolbox: PropTypes.object.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = { urlValue: props.toolbox.target.url };
+
+ this.onChange = this.onChange.bind(this);
+ this.onFocus = this.onFocus.bind(this);
+ this.onSubmit = this.onSubmit.bind(this);
+ }
+
+ componentDidMount() {
+ this.updateTitle();
+ }
+
+ updateTitle() {
+ const { L10N, debugTargetData, toolbox } = this.props;
+ const title = toolbox.target.name;
+ const descriptorTypeStr = L10N.getStr(
+ this.getAssetsForDebugDescriptorType().l10nId
+ );
+
+ const { connectionType } = debugTargetData;
+ if (connectionType === CONNECTION_TYPES.THIS_FIREFOX) {
+ toolbox.doc.title = L10N.getFormatStr(
+ "toolbox.debugTargetInfo.tabTitleLocal",
+ descriptorTypeStr,
+ title
+ );
+ } else {
+ const connectionTypeStr = L10N.getStr(
+ this.getAssetsForConnectionType().l10nId
+ );
+ toolbox.doc.title = L10N.getFormatStr(
+ "toolbox.debugTargetInfo.tabTitleRemote",
+ connectionTypeStr,
+ descriptorTypeStr,
+ title
+ );
+ }
+ }
+
+ getRuntimeText() {
+ const { debugTargetData, L10N } = this.props;
+ const { name, version } = debugTargetData.runtimeInfo;
+ const { connectionType } = debugTargetData;
+ const brandShorterName = L10N.getStr("brandShorterName");
+
+ return connectionType === CONNECTION_TYPES.THIS_FIREFOX
+ ? L10N.getFormatStr(
+ "toolbox.debugTargetInfo.runtimeLabel.thisRuntime",
+ brandShorterName,
+ version
+ )
+ : L10N.getFormatStr(
+ "toolbox.debugTargetInfo.runtimeLabel",
+ name,
+ version
+ );
+ }
+
+ getAssetsForConnectionType() {
+ const { connectionType } = this.props.debugTargetData;
+
+ switch (connectionType) {
+ case CONNECTION_TYPES.USB:
+ return {
+ image: "chrome://devtools/skin/images/aboutdebugging-usb-icon.svg",
+ l10nId: "toolbox.debugTargetInfo.connection.usb",
+ };
+ case CONNECTION_TYPES.NETWORK:
+ return {
+ image: "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg",
+ l10nId: "toolbox.debugTargetInfo.connection.network",
+ };
+ default:
+ return {};
+ }
+ }
+
+ getAssetsForDebugDescriptorType() {
+ const { descriptorType } = this.props.debugTargetData;
+
+ // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1520723
+ // Show actual favicon (currently toolbox.target.activeTab.favicon
+ // is unpopulated)
+ const favicon = "chrome://devtools/skin/images/globe.svg";
+
+ switch (descriptorType) {
+ case DESCRIPTOR_TYPES.EXTENSION:
+ return {
+ image: "chrome://devtools/skin/images/debugging-addons.svg",
+ l10nId: "toolbox.debugTargetInfo.targetType.extension",
+ };
+ case DESCRIPTOR_TYPES.PROCESS:
+ return {
+ image: "chrome://devtools/skin/images/settings.svg",
+ l10nId: "toolbox.debugTargetInfo.targetType.process",
+ };
+ case DESCRIPTOR_TYPES.TAB:
+ return {
+ image: favicon,
+ l10nId: "toolbox.debugTargetInfo.targetType.tab",
+ };
+ case DESCRIPTOR_TYPES.WORKER:
+ return {
+ image: "chrome://devtools/skin/images/debugging-workers.svg",
+ l10nId: "toolbox.debugTargetInfo.targetType.worker",
+ };
+ default:
+ return {};
+ }
+ }
+
+ onChange({ target }) {
+ this.setState({ urlValue: target.value });
+ }
+
+ onFocus({ target }) {
+ target.select();
+ }
+
+ onSubmit(event) {
+ event.preventDefault();
+ let url = this.state.urlValue;
+
+ if (!url || !url.length) {
+ return;
+ }
+
+ try {
+ // Get the URL from the fixup service:
+ const flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ const uriInfo = Services.uriFixup.getFixupURIInfo(url, flags);
+ url = uriInfo.fixedURI.spec;
+ } catch (ex) {
+ // The getFixupURIInfo service will throw an error if a malformed URI is
+ // produced from the input.
+ console.error(ex);
+ }
+
+ this.props.toolbox.target.navigateTo({ url });
+ }
+
+ shallRenderConnection() {
+ const { connectionType } = this.props.debugTargetData;
+ const renderableTypes = [CONNECTION_TYPES.USB, CONNECTION_TYPES.NETWORK];
+
+ return renderableTypes.includes(connectionType);
+ }
+
+ renderConnection() {
+ const { connectionType } = this.props.debugTargetData;
+ const { image, l10nId } = this.getAssetsForConnectionType();
+
+ return dom.span(
+ {
+ className: "iconized-label qa-connection-info",
+ },
+ dom.img({ src: image, alt: `${connectionType} icon` }),
+ this.props.L10N.getStr(l10nId)
+ );
+ }
+
+ renderRuntime() {
+ if (
+ !this.props.debugTargetData.runtimeInfo ||
+ (this.props.debugTargetData.connectionType ===
+ CONNECTION_TYPES.THIS_FIREFOX &&
+ this.props.debugTargetData.descriptorType ===
+ DESCRIPTOR_TYPES.EXTENSION)
+ ) {
+ // Skip the runtime render if no runtimeInfo is available.
+ // Runtime info is retrieved from the remote-client-manager, which might not be
+ // setup if about:devtools-toolbox was not opened from about:debugging.
+ //
+ // Also skip the runtime if we are debugging firefox itself, mainly to save some space.
+ return null;
+ }
+
+ const { icon, deviceName } = this.props.debugTargetData.runtimeInfo;
+
+ return dom.span(
+ {
+ className: "iconized-label qa-runtime-info",
+ },
+ dom.img({ src: icon, className: "channel-icon qa-runtime-icon" }),
+ dom.b({ className: "devtools-ellipsis-text" }, this.getRuntimeText()),
+ dom.span({ className: "devtools-ellipsis-text" }, deviceName)
+ );
+ }
+
+ renderTargetTitle() {
+ const title = this.props.toolbox.target.name;
+
+ const { image, l10nId } = this.getAssetsForDebugDescriptorType();
+
+ return dom.span(
+ {
+ className: "iconized-label debug-target-title",
+ },
+ dom.img({ src: image, alt: this.props.L10N.getStr(l10nId) }),
+ title
+ ? dom.b({ className: "devtools-ellipsis-text qa-target-title" }, title)
+ : null
+ );
+ }
+
+ renderTargetURI() {
+ const url = this.props.toolbox.target.url;
+ const { descriptorType } = this.props.debugTargetData;
+ const isURLEditable = descriptorType === DESCRIPTOR_TYPES.TAB;
+
+ return dom.span(
+ {
+ key: url,
+ className: "debug-target-url",
+ },
+ isURLEditable
+ ? this.renderTargetInput(url)
+ : dom.span(
+ { className: "debug-target-url-readonly devtools-ellipsis-text" },
+ url
+ )
+ );
+ }
+
+ renderTargetInput(url) {
+ return dom.form(
+ {
+ className: "debug-target-url-form",
+ onSubmit: this.onSubmit,
+ },
+ dom.input({
+ className: "devtools-textinput debug-target-url-input",
+ onChange: this.onChange,
+ onFocus: this.onFocus,
+ defaultValue: url,
+ })
+ );
+ }
+
+ renderAlwaysOnTopButton() {
+ // This is only displayed for local web extension debugging
+ const { descriptorType, connectionType } = this.props.debugTargetData;
+ const isLocalWebExtension =
+ descriptorType === DESCRIPTOR_TYPES.EXTENSION &&
+ connectionType === CONNECTION_TYPES.THIS_FIREFOX;
+ if (!isLocalWebExtension) {
+ return [];
+ }
+
+ const checked = this.props.alwaysOnTop;
+ const toolboxFocused = this.props.focusedState;
+ return [
+ Localized(
+ {
+ id: checked
+ ? "toolbox-always-on-top-enabled2"
+ : "toolbox-always-on-top-disabled2",
+ attrs: { title: true },
+ },
+ dom.button({
+ className:
+ `toolbox-always-on-top` +
+ (checked ? " checked" : "") +
+ (toolboxFocused ? " toolbox-is-focused" : ""),
+ onClick: this.props.toggleAlwaysOnTop,
+ })
+ ),
+ ];
+ }
+
+ renderNavigationButton(detail) {
+ const { L10N } = this.props;
+
+ return dom.button(
+ {
+ className: `iconized-label navigation-button ${detail.className}`,
+ onClick: detail.onClick,
+ title: L10N.getStr(detail.l10nId),
+ },
+ dom.img({
+ src: detail.icon,
+ alt: L10N.getStr(detail.l10nId),
+ })
+ );
+ }
+
+ renderNavigation() {
+ const { debugTargetData } = this.props;
+ const { descriptorType } = debugTargetData;
+
+ if (
+ descriptorType !== DESCRIPTOR_TYPES.TAB &&
+ descriptorType !== DESCRIPTOR_TYPES.EXTENSION
+ ) {
+ return null;
+ }
+
+ const items = [];
+
+ // There is little value in exposing back/forward for WebExtensions
+ if (
+ this.props.toolbox.target.getTrait("navigation") &&
+ descriptorType === DESCRIPTOR_TYPES.TAB
+ ) {
+ items.push(
+ this.renderNavigationButton({
+ className: "qa-back-button",
+ icon: "chrome://browser/skin/back.svg",
+ l10nId: "toolbox.debugTargetInfo.back",
+ onClick: () => this.props.toolbox.target.goBack(),
+ }),
+ this.renderNavigationButton({
+ className: "qa-forward-button",
+ icon: "chrome://browser/skin/forward.svg",
+ l10nId: "toolbox.debugTargetInfo.forward",
+ onClick: () => this.props.toolbox.target.goForward(),
+ })
+ );
+ }
+
+ items.push(
+ this.renderNavigationButton({
+ className: "qa-reload-button",
+ icon: "chrome://global/skin/icons/reload.svg",
+ l10nId: "toolbox.debugTargetInfo.reload",
+ onClick: () =>
+ this.props.toolbox.commands.targetCommand.reloadTopLevelTarget(),
+ })
+ );
+
+ return dom.div(
+ {
+ className: "debug-target-navigation",
+ },
+ ...items
+ );
+ }
+
+ render() {
+ return dom.header(
+ {
+ className: "debug-target-info qa-debug-target-info",
+ },
+ this.shallRenderConnection() ? this.renderConnection() : null,
+ this.renderRuntime(),
+ this.renderTargetTitle(),
+ this.renderNavigation(),
+ this.renderTargetURI(),
+ ...this.renderAlwaysOnTopButton()
+ );
+ }
+}
+
+module.exports = DebugTargetInfo;