summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js')
-rw-r--r--devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js249
1 files changed, 249 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js b/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js
new file mode 100644
index 0000000000..fd5ceca46d
--- /dev/null
+++ b/devtools/client/debugger/src/components/PrimaryPanes/SourcesTreeItem.js
@@ -0,0 +1,249 @@
+/* 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/>. */
+
+import React, { Component } from "devtools/client/shared/vendor/react";
+import { div, span } from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+import { connect } from "devtools/client/shared/vendor/react-redux";
+
+import SourceIcon from "../shared/SourceIcon";
+import AccessibleImage from "../shared/AccessibleImage";
+
+import {
+ getGeneratedSourceByURL,
+ isSourceOverridden,
+ getHideIgnoredSources,
+} from "../../selectors/index";
+import actions from "../../actions/index";
+
+import { sourceTypes } from "../../utils/source";
+import { createLocation } from "../../utils/location";
+import { safeDecodeItemName } from "../../utils/sources-tree/utils";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+class SourceTreeItem extends Component {
+ static get propTypes() {
+ return {
+ autoExpand: PropTypes.bool.isRequired,
+ depth: PropTypes.bool.isRequired,
+ expanded: PropTypes.bool.isRequired,
+ focusItem: PropTypes.func.isRequired,
+ focused: PropTypes.bool.isRequired,
+ hasMatchingGeneratedSource: PropTypes.bool.isRequired,
+ item: PropTypes.object.isRequired,
+ selectSourceItem: PropTypes.func.isRequired,
+ setExpanded: PropTypes.func.isRequired,
+ getParent: PropTypes.func.isRequired,
+ isOverridden: PropTypes.bool,
+ hideIgnoredSources: PropTypes.bool,
+ };
+ }
+
+ componentDidMount() {
+ const { autoExpand, item } = this.props;
+ if (autoExpand) {
+ this.props.setExpanded(item, true, false);
+ }
+ }
+
+ onClick = e => {
+ const { item, focusItem, selectSourceItem } = this.props;
+
+ focusItem(item);
+ if (item.type == "source") {
+ selectSourceItem(item);
+ }
+ };
+
+ onContextMenu = event => {
+ event.stopPropagation();
+ event.preventDefault();
+ this.props.showSourceTreeItemContextMenu(
+ event,
+ this.props.item,
+ this.props.depth,
+ this.props.setExpanded,
+ this.renderItemName()
+ );
+ };
+
+ renderItemArrow() {
+ const { item, expanded } = this.props;
+ return item.type != "source"
+ ? React.createElement(AccessibleImage, {
+ className: classnames("arrow", {
+ expanded,
+ }),
+ })
+ : span({
+ className: "img no-arrow",
+ });
+ }
+
+ renderIcon(item) {
+ if (item.type == "thread") {
+ const icon = item.thread.targetType.includes("worker")
+ ? "worker"
+ : "window";
+ return React.createElement(AccessibleImage, {
+ className: classnames(icon),
+ });
+ }
+ if (item.type == "group") {
+ if (item.groupName === "Webpack") {
+ return React.createElement(AccessibleImage, {
+ className: "webpack",
+ });
+ } else if (item.groupName === "Angular") {
+ return React.createElement(AccessibleImage, {
+ className: "angular",
+ });
+ }
+ // Check if the group relates to an extension.
+ // This happens when a webextension injects a content script.
+ if (item.isForExtensionSource) {
+ return React.createElement(AccessibleImage, {
+ className: "extension",
+ });
+ }
+ return React.createElement(AccessibleImage, {
+ className: "globe-small",
+ });
+ }
+ if (item.type == "directory") {
+ return React.createElement(AccessibleImage, {
+ className: "folder",
+ });
+ }
+ if (item.type == "source") {
+ const { source, sourceActor } = item;
+ return React.createElement(SourceIcon, {
+ location: createLocation({
+ source,
+ sourceActor,
+ }),
+ modifier: icon => {
+ // In the SourceTree, extension files should use the file-extension based icon,
+ // whereas we use the extension icon in other Components (eg. source tabs and breakpoints pane).
+ if (icon === "extension") {
+ return sourceTypes[source.displayURL.fileExtension] || "javascript";
+ }
+ return icon + (this.props.isOverridden ? " override" : "");
+ },
+ });
+ }
+ return null;
+ }
+ renderItemName() {
+ const { item } = this.props;
+
+ if (item.type == "thread") {
+ const { thread } = item;
+ return (
+ thread.name +
+ (thread.serviceWorkerStatus ? ` (${thread.serviceWorkerStatus})` : "")
+ );
+ }
+ if (item.type == "group") {
+ return safeDecodeItemName(item.groupName);
+ }
+ if (item.type == "directory") {
+ const parentItem = this.props.getParent(item);
+ return safeDecodeItemName(
+ item.path.replace(parentItem.path, "").replace(/^\//, "")
+ );
+ }
+ if (item.type == "source") {
+ const { displayURL } = item.source;
+ const name =
+ displayURL.filename + (displayURL.search ? displayURL.search : "");
+ return safeDecodeItemName(name);
+ }
+
+ return null;
+ }
+
+ renderItemTooltip() {
+ const { item } = this.props;
+
+ if (item.type == "thread") {
+ return item.thread.name;
+ }
+ if (item.type == "group") {
+ return item.groupName;
+ }
+ if (item.type == "directory") {
+ return item.path;
+ }
+ if (item.type == "source") {
+ return item.source.url;
+ }
+
+ return null;
+ }
+
+ render() {
+ const { item, focused, hasMatchingGeneratedSource, hideIgnoredSources } =
+ this.props;
+
+ if (hideIgnoredSources && item.isBlackBoxed) {
+ return null;
+ }
+ const suffix = hasMatchingGeneratedSource
+ ? span(
+ {
+ className: "suffix",
+ },
+ L10N.getStr("sourceFooter.mappedSuffix")
+ )
+ : null;
+ return div(
+ {
+ className: classnames("node", {
+ focused,
+ blackboxed: item.type == "source" && item.isBlackBoxed,
+ }),
+ key: item.path,
+ onClick: this.onClick,
+ onContextMenu: this.onContextMenu,
+ title: this.renderItemTooltip(),
+ },
+ this.renderItemArrow(),
+ this.renderIcon(item),
+ span(
+ {
+ className: "label",
+ },
+ this.renderItemName(),
+ suffix
+ )
+ );
+ }
+}
+
+function getHasMatchingGeneratedSource(state, source) {
+ if (!source || !source.isOriginal) {
+ return false;
+ }
+
+ return !!getGeneratedSourceByURL(state, source.url);
+}
+
+const mapStateToProps = (state, props) => {
+ const { item } = props;
+ if (item.type == "source") {
+ const { source } = item;
+ return {
+ hasMatchingGeneratedSource: getHasMatchingGeneratedSource(state, source),
+ isOverridden: isSourceOverridden(state, source),
+ hideIgnoredSources: getHideIgnoredSources(state),
+ };
+ }
+ return {};
+};
+
+export default connect(mapStateToProps, {
+ showSourceTreeItemContextMenu: actions.showSourceTreeItemContextMenu,
+})(SourceTreeItem);