Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this 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.bool.isRequired,
focusedState: PropTypes.bool,
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,
descriptorName: PropTypes.string.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;
// 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)
);
}
renderDescriptorName() {
const name = this.props.debugTargetData.descriptorName;
const { image, l10nId } = this.getAssetsForDebugDescriptorType();
return dom.span(
{
className: "iconized-label debug-descriptor-title",
},
dom.img({ src: image, alt: this.props.L10N.getStr(l10nId) }),
name
? dom.b(
{ className: "devtools-ellipsis-text qa-descriptor-title" },
name
)
: null
);
}
renderTargetURI() {
const { descriptorType } = this.props.debugTargetData;
const { url } = this.props.toolbox.target;
const isWebExtension = descriptorType === DESCRIPTOR_TYPES.EXTENSION;
// Avoid displaying the target url for web extension as it is always
// the fallback document URL. Keeps rendering the url component
// as it use flex to align the "always on top" button on the right.
if (isWebExtension) {
return dom.span({
className: "debug-target-url",
});
}
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.renderDescriptorName(),
this.renderNavigation(),
this.renderTargetURI(),
...this.renderAlwaysOnTopButton()
);
}
}
module.exports = DebugTargetInfo;