summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/components/Input/EvaluationContextSelector.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/components/Input/EvaluationContextSelector.js')
-rw-r--r--devtools/client/webconsole/components/Input/EvaluationContextSelector.js290
1 files changed, 290 insertions, 0 deletions
diff --git a/devtools/client/webconsole/components/Input/EvaluationContextSelector.js b/devtools/client/webconsole/components/Input/EvaluationContextSelector.js
new file mode 100644
index 0000000000..3842c0e7db
--- /dev/null
+++ b/devtools/client/webconsole/components/Input/EvaluationContextSelector.js
@@ -0,0 +1,290 @@
+/* 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";
+
+// React & Redux
+const {
+ Component,
+ 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 {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const targetActions = require("resource://devtools/shared/commands/target/actions/targets.js");
+const webconsoleActions = require("resource://devtools/client/webconsole/actions/index.js");
+
+const {
+ l10n,
+} = require("resource://devtools/client/webconsole/utils/messages.js");
+const targetSelectors = require("resource://devtools/shared/commands/target/selectors/targets.js");
+
+loader.lazyGetter(this, "TARGET_TYPES", function () {
+ return require("resource://devtools/shared/commands/target/target-command.js")
+ .TYPES;
+});
+
+// Additional Components
+const MenuButton = createFactory(
+ require("resource://devtools/client/shared/components/menu/MenuButton.js")
+);
+
+loader.lazyGetter(this, "MenuItem", function () {
+ return createFactory(
+ require("resource://devtools/client/shared/components/menu/MenuItem.js")
+ );
+});
+
+loader.lazyGetter(this, "MenuList", function () {
+ return createFactory(
+ require("resource://devtools/client/shared/components/menu/MenuList.js")
+ );
+});
+
+class EvaluationContextSelector extends Component {
+ static get propTypes() {
+ return {
+ selectTarget: PropTypes.func.isRequired,
+ onContextChange: PropTypes.func.isRequired,
+ selectedTarget: PropTypes.object,
+ lastTargetRefresh: PropTypes.number,
+ targets: PropTypes.array,
+ webConsoleUI: PropTypes.object.isRequired,
+ };
+ }
+
+ shouldComponentUpdate(nextProps) {
+ if (this.props.selectedTarget !== nextProps.selectedTarget) {
+ return true;
+ }
+
+ if (this.props.lastTargetRefresh !== nextProps.lastTargetRefresh) {
+ return true;
+ }
+
+ if (this.props.targets.length !== nextProps.targets.length) {
+ return true;
+ }
+
+ for (let i = 0; i < nextProps.targets.length; i++) {
+ const target = this.props.targets[i];
+ const nextTarget = nextProps.targets[i];
+ if (target.url != nextTarget.url || target.name != nextTarget.name) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.selectedTarget !== prevProps.selectedTarget) {
+ this.props.onContextChange();
+ }
+ }
+
+ getIcon(target) {
+ if (target.targetType === TARGET_TYPES.FRAME) {
+ return "chrome://devtools/content/debugger/images/globe-small.svg";
+ }
+
+ if (
+ target.targetType === TARGET_TYPES.WORKER ||
+ target.targetType === TARGET_TYPES.SHARED_WORKER ||
+ target.targetType === TARGET_TYPES.SERVICE_WORKER
+ ) {
+ return "chrome://devtools/content/debugger/images/worker.svg";
+ }
+
+ if (target.targetType === TARGET_TYPES.PROCESS) {
+ return "chrome://devtools/content/debugger/images/window.svg";
+ }
+
+ return null;
+ }
+
+ renderMenuItem(target) {
+ const { selectTarget, selectedTarget } = this.props;
+
+ const label = target.isTopLevel
+ ? l10n.getStr("webconsole.input.selector.top")
+ : target.name;
+
+ return MenuItem({
+ key: `webconsole-evaluation-selector-item-${target.actorID}`,
+ className: "menu-item webconsole-evaluation-selector-item",
+ type: "checkbox",
+ checked: selectedTarget ? selectedTarget == target : target.isTopLevel,
+ label,
+ tooltip: target.url || target.name,
+ icon: this.getIcon(target),
+ onClick: () => selectTarget(target.actorID),
+ });
+ }
+
+ renderMenuItems() {
+ const { targets } = this.props;
+
+ // Let's sort the targets (using "numeric" so Content processes are ordered by PID).
+ const collator = new Intl.Collator("en", { numeric: true });
+ targets.sort((a, b) => collator.compare(a.name, b.name));
+
+ let mainTarget;
+ const sections = {
+ [TARGET_TYPES.FRAME]: [],
+ [TARGET_TYPES.WORKER]: [],
+ [TARGET_TYPES.SHARED_WORKER]: [],
+ [TARGET_TYPES.SERVICE_WORKER]: [],
+ };
+ // When in Browser Toolbox, we want to display the process targets with the frames
+ // in the same process as a group
+ // e.g.
+ // |------------------------------|
+ // | Top |
+ // | -----------------------------|
+ // | (pid 1234) priviledgedabout |
+ // | New Tab |
+ // | -----------------------------|
+ // | (pid 5678) web |
+ // | cnn.com |
+ // | -----------------------------|
+ // | RemoteSettingWorker.js |
+ // |------------------------------|
+ //
+ // This object will be keyed by PID, and each property will be an object with a
+ // `process` property (for the process target item), and a `frames` property (and array
+ // for all the frame target items).
+ const processes = {};
+
+ const { webConsoleUI } = this.props;
+ const handleProcessTargets =
+ webConsoleUI.isBrowserConsole || webConsoleUI.isBrowserToolboxConsole;
+
+ for (const target of targets) {
+ const menuItem = this.renderMenuItem(target);
+
+ if (target.isTopLevel) {
+ mainTarget = menuItem;
+ } else if (target.targetType == TARGET_TYPES.PROCESS) {
+ if (!processes[target.processID]) {
+ processes[target.processID] = { frames: [] };
+ }
+ processes[target.processID].process = menuItem;
+ } else if (
+ target.targetType == TARGET_TYPES.FRAME &&
+ handleProcessTargets &&
+ target.processID
+ ) {
+ // The associated process target might not have been handled yet, so make sure
+ // to create it.
+ if (!processes[target.processID]) {
+ processes[target.processID] = { frames: [] };
+ }
+ processes[target.processID].frames.push(menuItem);
+ } else {
+ sections[target.targetType].push(menuItem);
+ }
+ }
+
+ // Note that while debugging popups, we might have a small period
+ // of time where we don't have any top level target when we reload
+ // the original tab
+ const items = mainTarget ? [mainTarget] : [];
+
+ // Handle PROCESS targets sections first, as we want to display the associated frames
+ // below the process to group them.
+ if (processes) {
+ for (const [pid, { process, frames }] of Object.entries(processes)) {
+ items.push(dom.hr({ role: "menuseparator", key: `${pid}-separator` }));
+ if (process) {
+ items.push(process);
+ }
+ if (frames) {
+ items.push(...frames);
+ }
+ }
+ }
+
+ for (const [targetType, menuItems] of Object.entries(sections)) {
+ if (menuItems.length) {
+ items.push(
+ dom.hr({ role: "menuseparator", key: `${targetType}-separator` }),
+ ...menuItems
+ );
+ }
+ }
+
+ return MenuList(
+ { id: "webconsole-console-evaluation-context-selector-menu-list" },
+ items
+ );
+ }
+
+ getLabel() {
+ const { selectedTarget } = this.props;
+
+ if (!selectedTarget || selectedTarget.isTopLevel) {
+ return l10n.getStr("webconsole.input.selector.top");
+ }
+
+ return selectedTarget.name;
+ }
+
+ render() {
+ const { webConsoleUI, targets, selectedTarget } = this.props;
+
+ // Don't render if there's only one target.
+ // Also bail out if the console is being destroyed (where WebConsoleUI.wrapper gets
+ // nullified).
+ if (targets.length <= 1 || !webConsoleUI.wrapper) {
+ return null;
+ }
+
+ const doc = webConsoleUI.document;
+ const { toolbox } = webConsoleUI.wrapper;
+
+ return MenuButton(
+ {
+ menuId: "webconsole-input-evaluationsButton",
+ toolboxDoc: toolbox ? toolbox.doc : doc,
+ label: this.getLabel(),
+ className:
+ "webconsole-evaluation-selector-button devtools-button devtools-dropdown-button" +
+ (selectedTarget && !selectedTarget.isTopLevel ? " checked" : ""),
+ title: l10n.getStr("webconsole.input.selector.tooltip"),
+ },
+ // We pass the children in a function so we don't require the MenuItem and MenuList
+ // components until we need to display them (i.e. when the button is clicked).
+ () => this.renderMenuItems()
+ );
+ }
+}
+
+const toolboxConnected = connect(
+ state => ({
+ targets: targetSelectors.getToolboxTargets(state),
+ selectedTarget: targetSelectors.getSelectedTarget(state),
+ lastTargetRefresh: targetSelectors.getLastTargetRefresh(state),
+ }),
+ dispatch => ({
+ selectTarget: actorID => dispatch(targetActions.selectTarget(actorID)),
+ }),
+ undefined,
+ { storeKey: "target-store" }
+)(EvaluationContextSelector);
+
+module.exports = connect(
+ state => state,
+ dispatch => ({
+ onContextChange: () => {
+ dispatch(
+ webconsoleActions.updateInstantEvaluationResultForCurrentExpression()
+ );
+ dispatch(webconsoleActions.autocompleteClear());
+ },
+ })
+)(toolboxConnected);