diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/webconsole/components/Input/EvaluationContextSelector.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/webconsole/components/Input/EvaluationContextSelector.js')
-rw-r--r-- | devtools/client/webconsole/components/Input/EvaluationContextSelector.js | 290 |
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); |