summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/components/shared
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/debugger/src/components/shared
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/debugger/src/components/shared')
-rw-r--r--devtools/client/debugger/src/components/shared/AccessibleImage.css201
-rw-r--r--devtools/client/debugger/src/components/shared/AccessibleImage.js21
-rw-r--r--devtools/client/debugger/src/components/shared/Accordion.css107
-rw-r--r--devtools/client/debugger/src/components/shared/Accordion.js89
-rw-r--r--devtools/client/debugger/src/components/shared/Badge.css16
-rw-r--r--devtools/client/debugger/src/components/shared/Badge.js30
-rw-r--r--devtools/client/debugger/src/components/shared/BracketArrow.css64
-rw-r--r--devtools/client/debugger/src/components/shared/BracketArrow.js28
-rw-r--r--devtools/client/debugger/src/components/shared/Button/CloseButton.js30
-rw-r--r--devtools/client/debugger/src/components/shared/Button/CommandBarButton.js55
-rw-r--r--devtools/client/debugger/src/components/shared/Button/PaneToggleButton.js58
-rw-r--r--devtools/client/debugger/src/components/shared/Button/index.js9
-rw-r--r--devtools/client/debugger/src/components/shared/Button/moz.build15
-rw-r--r--devtools/client/debugger/src/components/shared/Button/styles/CloseButton.css35
-rw-r--r--devtools/client/debugger/src/components/shared/Button/styles/CommandBarButton.css73
-rw-r--r--devtools/client/debugger/src/components/shared/Button/styles/PaneToggleButton.css29
-rw-r--r--devtools/client/debugger/src/components/shared/Button/styles/moz.build8
-rw-r--r--devtools/client/debugger/src/components/shared/Button/tests/CloseButton.spec.js31
-rw-r--r--devtools/client/debugger/src/components/shared/Button/tests/CommandBarButton.spec.js44
-rw-r--r--devtools/client/debugger/src/components/shared/Button/tests/PaneToggleButton.spec.js51
-rw-r--r--devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CloseButton.spec.js.snap13
-rw-r--r--devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CommandBarButton.spec.js.snap18
-rw-r--r--devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/PaneToggleButton.spec.js.snap13
-rw-r--r--devtools/client/debugger/src/components/shared/Dropdown.css97
-rw-r--r--devtools/client/debugger/src/components/shared/Dropdown.js74
-rw-r--r--devtools/client/debugger/src/components/shared/Modal.css47
-rw-r--r--devtools/client/debugger/src/components/shared/Modal.js45
-rw-r--r--devtools/client/debugger/src/components/shared/Popover.css32
-rw-r--r--devtools/client/debugger/src/components/shared/Popover.js324
-rw-r--r--devtools/client/debugger/src/components/shared/PreviewFunction.css23
-rw-r--r--devtools/client/debugger/src/components/shared/PreviewFunction.js108
-rw-r--r--devtools/client/debugger/src/components/shared/ResultList.css131
-rw-r--r--devtools/client/debugger/src/components/shared/ResultList.js102
-rw-r--r--devtools/client/debugger/src/components/shared/SearchInput.css223
-rw-r--r--devtools/client/debugger/src/components/shared/SearchInput.js362
-rw-r--r--devtools/client/debugger/src/components/shared/SmartGap.js170
-rw-r--r--devtools/client/debugger/src/components/shared/SourceIcon.css176
-rw-r--r--devtools/client/debugger/src/components/shared/SourceIcon.js71
-rw-r--r--devtools/client/debugger/src/components/shared/menu.css55
-rw-r--r--devtools/client/debugger/src/components/shared/moz.build23
-rw-r--r--devtools/client/debugger/src/components/shared/tests/Accordion.spec.js47
-rw-r--r--devtools/client/debugger/src/components/shared/tests/Badge.spec.js19
-rw-r--r--devtools/client/debugger/src/components/shared/tests/BracketArrow.spec.js24
-rw-r--r--devtools/client/debugger/src/components/shared/tests/Dropdown.spec.js21
-rw-r--r--devtools/client/debugger/src/components/shared/tests/Modal.spec.js56
-rw-r--r--devtools/client/debugger/src/components/shared/tests/Popover.spec.js212
-rw-r--r--devtools/client/debugger/src/components/shared/tests/PreviewFunction.spec.js130
-rw-r--r--devtools/client/debugger/src/components/shared/tests/ResultList.spec.js48
-rw-r--r--devtools/client/debugger/src/components/shared/tests/SearchInput.spec.js126
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/Accordion.spec.js.snap81
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/Badge.spec.js.snap9
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/BracketArrow.spec.js.snap27
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/Dropdown.spec.js.snap34
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/Modal.spec.js.snap13
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/Popover.spec.js.snap549
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/PreviewFunction.spec.js.snap23
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/ResultList.spec.js.snap55
-rw-r--r--devtools/client/debugger/src/components/shared/tests/__snapshots__/SearchInput.spec.js.snap267
58 files changed, 4842 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/shared/AccessibleImage.css b/devtools/client/debugger/src/components/shared/AccessibleImage.css
new file mode 100644
index 0000000000..4ba5f1326a
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/AccessibleImage.css
@@ -0,0 +1,201 @@
+/* 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/>. */
+
+.img {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+ /* use background-color for the icon color, and mask-image for its shape */
+ background-color: var(--theme-icon-color);
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ /* multicolor icons use background-image */
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: contain;
+ /* do not let images shrink when used as flex children */
+ flex-shrink: 0;
+}
+
+/* Expand arrow icon */
+.img.arrow {
+ width: 10px;
+ height: 10px;
+ mask-image: url(chrome://devtools/content/debugger/images/arrow.svg);
+ /* we may override the width/height in specific contexts to make the
+ clickable area bigger, but we should always keep the mask size 10x10 */
+ mask-size: 10px 10px;
+ background-color: var(--theme-icon-dimmed-color);
+ transform: rotate(-90deg);
+ transition: transform 180ms var(--animation-curve);
+}
+
+.img.arrow:dir(rtl) {
+ transform: rotate(90deg);
+}
+
+.img.arrow.expanded {
+ /* icon should always point to the bottom (default) when expanded,
+ regardless of the text direction */
+ transform: none !important;
+}
+
+.img.arrow-down {
+ mask-image: url(chrome://devtools/content/debugger/images/arrow-down.svg);
+}
+
+.img.arrow-up {
+ mask-image: url(chrome://devtools/content/debugger/images/arrow-up.svg);
+}
+
+.img.blackBox {
+ mask-image: url(chrome://devtools/content/debugger/images/blackBox.svg);
+}
+
+.img.breadcrumb {
+ mask-image: url(chrome://devtools/content/debugger/images/breadcrumbs-divider.svg);
+}
+
+.img.close {
+ mask-image: url(chrome://devtools/skin/images/close.svg);
+}
+
+.img.disable-pausing {
+ mask-image: url(chrome://devtools/content/debugger/images/disable-pausing.svg);
+}
+
+.img.enable-pausing {
+ mask-image: url(chrome://devtools/content/debugger/images/enable-pausing.svg);
+ background-color: var(--theme-icon-checked-color);
+}
+
+.img.globe {
+ mask-image: url(chrome://devtools/content/debugger/images/globe.svg);
+}
+
+.img.globe-small {
+ mask-image: url(chrome://devtools/content/debugger/images/globe-small.svg);
+ mask-size: 12px 12px;
+}
+
+.img.window {
+ mask-image: url(chrome://devtools/content/debugger/images/window.svg);
+}
+
+.img.file {
+ mask-image: url(chrome://devtools/content/debugger/images/file-small.svg);
+ mask-size: 12px 12px;
+}
+
+.img.folder {
+ mask-image: url(chrome://devtools/content/debugger/images/folder.svg);
+}
+
+.img.home {
+ mask-image: url(chrome://devtools/content/debugger/images/home.svg);
+}
+
+.img.info {
+ mask-image: url(chrome://devtools/skin/images/info.svg);
+}
+
+.img.loader {
+ background-image: url(chrome://devtools/content/debugger/images/loader.svg);
+ -moz-context-properties: fill;
+ fill: var(--theme-icon-color);
+ background-color: unset;
+}
+
+.img.more-tabs {
+ mask-image: url(chrome://devtools/content/debugger/images/command-chevron.svg);
+}
+
+html[dir="rtl"] .img.more-tabs {
+ transform: scaleX(-1);
+}
+
+.img.sourcemap {
+ background-image: url(chrome://devtools/content/debugger/images/sourcemap.svg);
+ -moz-context-properties: fill;
+ fill: var(--theme-icon-warning-color);
+ background-color: unset;
+}
+
+.img.next {
+ mask-image: url(chrome://devtools/content/debugger/images/next.svg);
+}
+
+.img.next-circle {
+ mask-image: url(chrome://devtools/content/debugger/images/next-circle.svg);
+}
+
+.img.pane-collapse {
+ mask-image: url(chrome://devtools/content/debugger/images/pane-collapse.svg);
+}
+
+.img.pane-expand {
+ mask-image: url(chrome://devtools/content/debugger/images/pane-expand.svg);
+}
+
+.img.pause {
+ mask-image: url(chrome://devtools/content/debugger/images/pause.svg);
+}
+
+.img.plus {
+ mask-image: url(chrome://devtools/skin/images/add.svg);
+}
+
+.img.prettyPrint {
+ background-image: url(chrome://devtools/content/debugger/images/prettyPrint.svg);
+ background-size: 14px 14px;
+ background-color: transparent !important;
+ fill: var(--theme-icon-color);
+ -moz-context-properties: fill;
+}
+
+.img.removeAll {
+ mask-image: url(chrome://devtools/skin/images/clear.svg)
+}
+
+.img.refresh {
+ mask-image: url(chrome://devtools/skin/images/reload.svg);
+}
+
+.img.resume {
+ mask-image: url(resource://devtools-shared-images/resume.svg);
+}
+
+.img.search {
+ mask-image: url(chrome://devtools/content/debugger/images/search.svg);
+}
+
+.img.shortcuts {
+ mask-image: url(chrome://devtools/content/debugger/images/help.svg);
+}
+
+.img.spin {
+ animation: spin 0.5s linear infinite;
+}
+
+.img.stepIn {
+ mask-image: url(chrome://devtools/content/debugger/images/stepIn.svg);
+}
+
+.img.stepOut {
+ mask-image: url(chrome://devtools/content/debugger/images/stepOut.svg);
+}
+
+.img.stepOver {
+ mask-image: url(resource://devtools-shared-images/stepOver.svg);
+}
+
+.img.tab {
+ mask-image: url(chrome://devtools/content/debugger/images/tab.svg);
+}
+
+.img.worker {
+ mask-image: url(chrome://devtools/content/debugger/images/worker.svg);
+}
diff --git a/devtools/client/debugger/src/components/shared/AccessibleImage.js b/devtools/client/debugger/src/components/shared/AccessibleImage.js
new file mode 100644
index 0000000000..e3a59573ea
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/AccessibleImage.js
@@ -0,0 +1,21 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+const AccessibleImage = props => {
+ return React.createElement("span", {
+ ...props,
+ className: classnames("img", props.className),
+ });
+};
+
+AccessibleImage.propTypes = {
+ className: PropTypes.string.isRequired,
+};
+
+export default AccessibleImage;
diff --git a/devtools/client/debugger/src/components/shared/Accordion.css b/devtools/client/debugger/src/components/shared/Accordion.css
new file mode 100644
index 0000000000..d970527014
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Accordion.css
@@ -0,0 +1,107 @@
+/* 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/>. */
+
+.accordion {
+ background-color: var(--theme-sidebar-background);
+ width: 100%;
+ list-style-type: none;
+ padding: 0px;
+ margin-top: 0px;
+}
+
+.accordion ._header {
+ background-color: var(--theme-accordion-header-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ display: flex;
+ column-gap: 8px;
+ font-size: 12px;
+ line-height: calc(16 / 12);
+ padding: 4px 6px;
+ width: 100%;
+ align-items: center;
+ margin: 0px;
+ font-weight: normal;
+ cursor: default;
+ user-select: none;
+}
+
+.accordion ._header:hover {
+ background-color: var(--theme-accordion-header-hover);
+}
+
+
+.accordion ._header .header-label {
+ flex-grow: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: var(--theme-toolbar-color);
+ background: transparent;
+ padding: 0;
+
+ /* align expand arrow and button text */
+ display: flex;
+ align-items: center;
+ gap: 4px;
+
+ &:hover {
+ background: transparent;
+ }
+
+ /* The expand arrow needs to be displayed inside the button to be accessible */
+ &::before {
+ content: "";
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ background-image: url(chrome://devtools/content/debugger/images/arrow.svg);
+ background-size: contain;
+ -moz-context-properties: fill;
+ fill: var(--theme-icon-dimmed-color);
+ rotate: -90deg;
+ transition: rotate 180ms var(--animation-curve);
+
+ &:dir(rtl) {
+ rotate: 90deg;
+ }
+ }
+
+ &[aria-expanded="true"]::before {
+ /* icon should always point to the bottom (default) when expanded,
+ regardless of the text direction */
+ rotate: 0deg !important;
+ }
+}
+
+.accordion ._header .header-buttons {
+ display: flex;
+ margin-inline-start: auto;
+}
+
+.accordion ._header .header-buttons button {
+ color: var(--theme-body-color);
+ border: none;
+ background: none;
+ padding: 0;
+ margin: 0 2px;
+ width: 16px;
+ height: 16px;
+}
+
+.accordion ._header .header-buttons button::-moz-focus-inner {
+ border: none;
+}
+
+.accordion ._header .header-buttons button .img {
+ display: block;
+}
+
+.accordion ._content {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ font-size: var(--theme-body-font-size);
+}
+
+.accordion div:last-child ._content {
+ border-bottom: none;
+}
diff --git a/devtools/client/debugger/src/components/shared/Accordion.js b/devtools/client/debugger/src/components/shared/Accordion.js
new file mode 100644
index 0000000000..3b5d5ae516
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Accordion.js
@@ -0,0 +1,89 @@
+/* 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 { cloneElement, Component } from "devtools/client/shared/vendor/react";
+import {
+ aside,
+ button,
+ div,
+ h2,
+} from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+class Accordion extends Component {
+ static get propTypes() {
+ return {
+ items: PropTypes.array.isRequired,
+ };
+ }
+
+ handleHeaderClick(i) {
+ const item = this.props.items[i];
+ const opened = !item.opened;
+ item.opened = opened;
+
+ if (item.onToggle) {
+ item.onToggle(opened);
+ }
+
+ // We force an update because otherwise the accordion
+ // would not re-render
+ this.forceUpdate();
+ }
+
+ renderContainer = (item, i) => {
+ const { opened } = item;
+ const contentElementId = `${item.id}-content`;
+
+ return aside(
+ {
+ className: item.className,
+ key: item.id,
+ "aria-labelledby": item.id,
+ role: item.role,
+ },
+ h2(
+ {
+ className: "_header",
+ },
+ button(
+ {
+ id: item.id,
+ className: "header-label",
+ "aria-expanded": `${opened ? "true" : "false"}`,
+ "aria-controls": opened ? contentElementId : undefined,
+ onClick: () => this.handleHeaderClick(i),
+ },
+ item.header
+ ),
+ item.buttons
+ ? div(
+ {
+ className: "header-buttons",
+ },
+ item.buttons
+ )
+ : null
+ ),
+ opened &&
+ div(
+ {
+ className: "_content",
+ id: contentElementId,
+ },
+ cloneElement(item.component, item.componentProps || {})
+ )
+ );
+ };
+ render() {
+ return div(
+ {
+ className: "accordion",
+ },
+ this.props.items.map(this.renderContainer)
+ );
+ }
+}
+
+export default Accordion;
diff --git a/devtools/client/debugger/src/components/shared/Badge.css b/devtools/client/debugger/src/components/shared/Badge.css
new file mode 100644
index 0000000000..f52d32edf4
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Badge.css
@@ -0,0 +1,16 @@
+/* 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/>. */
+
+.badge {
+ --size: 17px;
+ --radius: calc(var(--size) / 2);
+ height: var(--size);
+ min-width: var(--size);
+ line-height: var(--size);
+ background: var(--theme-toolbar-background-hover);
+ color: var(--theme-body-color);
+ border-radius: var(--radius);
+ padding: 0 4px;
+ font-size: 0.9em;
+}
diff --git a/devtools/client/debugger/src/components/shared/Badge.js b/devtools/client/debugger/src/components/shared/Badge.js
new file mode 100644
index 0000000000..72571c0f58
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Badge.js
@@ -0,0 +1,30 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+class Badge extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ static get propTypes() {
+ return {
+ badgeText: PropTypes.node.isRequired,
+ };
+ }
+
+ render() {
+ return React.createElement(
+ "span",
+ {
+ className: "badge text-white text-center",
+ },
+ this.props.badgeText
+ );
+ }
+}
+
+export default Badge;
diff --git a/devtools/client/debugger/src/components/shared/BracketArrow.css b/devtools/client/debugger/src/components/shared/BracketArrow.css
new file mode 100644
index 0000000000..afca888371
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/BracketArrow.css
@@ -0,0 +1,64 @@
+/* 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/>. */
+
+.bracket-arrow {
+ position: absolute;
+ pointer-events: none;
+}
+
+.bracket-arrow::before,
+.bracket-arrow::after {
+ content: "";
+ height: 0;
+ width: 0;
+ position: absolute;
+ border: 7px solid transparent;
+}
+
+.bracket-arrow.up::before {
+ border-bottom-color: var(--theme-splitter-color);
+ top: -1px;
+}
+
+.theme-dark .bracket-arrow.up::before {
+ border-bottom-color: var(--theme-body-color);
+}
+
+.bracket-arrow.up::after {
+ border-bottom-color: var(--theme-body-background);
+ top: 0px;
+}
+
+.bracket-arrow.down::before {
+ border-bottom-color: transparent;
+ border-top-color: var(--theme-splitter-color);
+ top: 0px;
+}
+
+.theme-dark .bracket-arrow.down::before {
+ border-top-color: var(--theme-body-color);
+}
+
+.bracket-arrow.down::after {
+ border-bottom-color: transparent;
+ border-top-color: var(--theme-body-background);
+ top: -1px;
+}
+
+.bracket-arrow.left::before {
+ border-left-color: transparent;
+ border-right-color: var(--theme-splitter-color);
+ top: 0px;
+}
+
+.theme-dark .bracket-arrow.left::before {
+ border-right-color: var(--theme-body-color);
+}
+
+.bracket-arrow.left::after {
+ border-left-color: transparent;
+ border-right-color: var(--theme-body-background);
+ top: 0px;
+ left: 1px;
+}
diff --git a/devtools/client/debugger/src/components/shared/BracketArrow.js b/devtools/client/debugger/src/components/shared/BracketArrow.js
new file mode 100644
index 0000000000..40e2cda6c4
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/BracketArrow.js
@@ -0,0 +1,28 @@
+/* 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 { div } from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+const BracketArrow = ({ orientation, left, top, bottom }) => {
+ return div({
+ className: classnames("bracket-arrow", orientation || "up"),
+ style: {
+ left,
+ top,
+ bottom,
+ },
+ });
+};
+
+BracketArrow.propTypes = {
+ bottom: PropTypes.number,
+ left: PropTypes.number,
+ orientation: PropTypes.string.isRequired,
+ top: PropTypes.number,
+};
+
+export default BracketArrow;
diff --git a/devtools/client/debugger/src/components/shared/Button/CloseButton.js b/devtools/client/debugger/src/components/shared/Button/CloseButton.js
new file mode 100644
index 0000000000..a8f66de60d
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/CloseButton.js
@@ -0,0 +1,30 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { button } from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+import AccessibleImage from "../AccessibleImage";
+
+function CloseButton({ handleClick, buttonClass, tooltip }) {
+ return button(
+ {
+ className: buttonClass ? `close-btn ${buttonClass}` : "close-btn",
+ onClick: handleClick,
+ title: tooltip,
+ },
+ React.createElement(AccessibleImage, {
+ className: "close",
+ })
+ );
+}
+
+CloseButton.propTypes = {
+ buttonClass: PropTypes.string,
+ handleClick: PropTypes.func.isRequired,
+ tooltip: PropTypes.string,
+};
+
+export default CloseButton;
diff --git a/devtools/client/debugger/src/components/shared/Button/CommandBarButton.js b/devtools/client/debugger/src/components/shared/Button/CommandBarButton.js
new file mode 100644
index 0000000000..4b0b52e186
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/CommandBarButton.js
@@ -0,0 +1,55 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { button } from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+import AccessibleImage from "../AccessibleImage";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+export function debugBtn(
+ onClick,
+ type,
+ className,
+ tooltip,
+ disabled = false,
+ ariaPressed = false
+) {
+ return React.createElement(
+ CommandBarButton,
+ {
+ className: classnames(type, className),
+ disabled: disabled,
+ key: type,
+ onClick: onClick,
+ pressed: ariaPressed,
+ title: tooltip,
+ },
+ React.createElement(AccessibleImage, {
+ className: type,
+ })
+ );
+}
+const CommandBarButton = props => {
+ const { children, className, pressed = false, ...rest } = props;
+
+ return button(
+ {
+ "aria-pressed": pressed,
+ className: classnames("command-bar-button", className),
+ ...rest,
+ },
+ children
+ );
+};
+
+CommandBarButton.propTypes = {
+ children: PropTypes.node.isRequired,
+ className: PropTypes.string.isRequired,
+ pressed: PropTypes.bool,
+};
+
+export default CommandBarButton;
diff --git a/devtools/client/debugger/src/components/shared/Button/PaneToggleButton.js b/devtools/client/debugger/src/components/shared/Button/PaneToggleButton.js
new file mode 100644
index 0000000000..ad003552ad
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/PaneToggleButton.js
@@ -0,0 +1,58 @@
+/* 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, { PureComponent } from "devtools/client/shared/vendor/react";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+import AccessibleImage from "../AccessibleImage";
+import { CommandBarButton } from "./index";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+class PaneToggleButton extends PureComponent {
+ static defaultProps = {
+ horizontal: false,
+ position: "start",
+ };
+
+ static get propTypes() {
+ return {
+ collapsed: PropTypes.bool.isRequired,
+ handleClick: PropTypes.func.isRequired,
+ horizontal: PropTypes.bool.isRequired,
+ position: PropTypes.oneOf(["start", "end"]).isRequired,
+ };
+ }
+
+ label(position, collapsed) {
+ switch (position) {
+ case "start":
+ return L10N.getStr(collapsed ? "expandSources" : "collapseSources");
+ case "end":
+ return L10N.getStr(
+ collapsed ? "expandBreakpoints" : "collapseBreakpoints"
+ );
+ }
+ return null;
+ }
+
+ render() {
+ const { position, collapsed, horizontal, handleClick } = this.props;
+ return React.createElement(
+ CommandBarButton,
+ {
+ className: classnames("toggle-button", position, {
+ collapsed,
+ vertical: !horizontal,
+ }),
+ onClick: () => handleClick(position, !collapsed),
+ title: this.label(position, collapsed),
+ },
+ React.createElement(AccessibleImage, {
+ className: collapsed ? "pane-expand" : "pane-collapse",
+ })
+ );
+ }
+}
+
+export default PaneToggleButton;
diff --git a/devtools/client/debugger/src/components/shared/Button/index.js b/devtools/client/debugger/src/components/shared/Button/index.js
new file mode 100644
index 0000000000..df7976ba90
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/index.js
@@ -0,0 +1,9 @@
+/* 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 CloseButton from "./CloseButton";
+import CommandBarButton, { debugBtn } from "./CommandBarButton";
+import PaneToggleButton from "./PaneToggleButton";
+
+export { CloseButton, CommandBarButton, debugBtn, PaneToggleButton };
diff --git a/devtools/client/debugger/src/components/shared/Button/moz.build b/devtools/client/debugger/src/components/shared/Button/moz.build
new file mode 100644
index 0000000000..c6e652d5dc
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+ "styles",
+]
+
+CompiledModules(
+ "CloseButton.js",
+ "CommandBarButton.js",
+ "index.js",
+ "PaneToggleButton.js",
+)
diff --git a/devtools/client/debugger/src/components/shared/Button/styles/CloseButton.css b/devtools/client/debugger/src/components/shared/Button/styles/CloseButton.css
new file mode 100644
index 0000000000..c2d8df6d38
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/styles/CloseButton.css
@@ -0,0 +1,35 @@
+/* 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/>. */
+
+.close-btn {
+ width: 16px;
+ height: 16px;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ padding: 1px;
+ color: var(--theme-icon-color);
+}
+
+.close-btn:hover {
+ color: var(--theme-selection-color);
+ background-color: var(--theme-selection-background);
+}
+
+.close-btn .img {
+ display: block;
+ width: 12px;
+ height: 12px;
+ /* inherit the button's text color for the icon's color */
+ background-color: currentColor;
+}
+
+.close-btn.big {
+ width: 20px;
+ height: 20px;
+}
+
+.close-btn.big .img {
+ width: 16px;
+ height: 16px;
+}
diff --git a/devtools/client/debugger/src/components/shared/Button/styles/CommandBarButton.css b/devtools/client/debugger/src/components/shared/Button/styles/CommandBarButton.css
new file mode 100644
index 0000000000..12e53e6fc5
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/styles/CommandBarButton.css
@@ -0,0 +1,73 @@
+/* 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/>. */
+
+.command-bar-button {
+ appearance: none;
+ background: transparent;
+ border: none;
+ display: inline-block;
+ text-align: center;
+ position: relative;
+ padding: 0px 5px;
+ fill: currentColor;
+ min-width: 30px;
+ /* Adjust outline so it's not clipped */
+ outline-offset: -3px;
+}
+
+.command-bar-button:disabled {
+ opacity: 0.6;
+ cursor: default;
+}
+
+.command-bar-button:not(.disabled):hover,
+.devtools-button.debugger-settings-menu-button:empty:enabled:not([aria-expanded="true"]):hover {
+ background: var(--theme-toolbar-background-hover);
+}
+
+.theme-dark .command-bar-button:not(.disabled):hover,
+.devtools-button.debugger-settings-menu-button:empty:enabled:not([aria-expanded="true"]):hover {
+ background: var(--theme-toolbar-hover);
+}
+
+:root.theme-dark .command-bar-button {
+ color: var(--theme-body-color);
+}
+
+.command-bar-button > * {
+ width: 16px;
+ height: 16px;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+/**
+ * Settings icon and menu
+ */
+.devtools-button.debugger-settings-menu-button {
+ border-radius: 0;
+ margin: 0;
+ padding: 0;
+}
+
+.devtools-button.debugger-settings-menu-button::before {
+ background-image: url("chrome://devtools/skin/images/settings.svg");
+}
+
+.devtools-button.debugger-trace-menu-button::before {
+ background-image: url(chrome://devtools/content/debugger/images/trace.svg);
+}
+.devtools-button.debugger-trace-menu-button:is(.active, .pending)::before {
+ fill: var(--theme-icon-checked-color);
+}
+.devtools-button.debugger-trace-menu-button.pending::after
+{
+ content: url("chrome://global/skin/icons/badge-blue.svg");
+ width: 14px;
+ height: 14px;
+ display: block;
+ position: absolute;
+ bottom: -2px;
+ right: 0;
+}
diff --git a/devtools/client/debugger/src/components/shared/Button/styles/PaneToggleButton.css b/devtools/client/debugger/src/components/shared/Button/styles/PaneToggleButton.css
new file mode 100644
index 0000000000..d8a2495408
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/styles/PaneToggleButton.css
@@ -0,0 +1,29 @@
+/* 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/>. */
+
+.toggle-button {
+ padding: 4px 6px;
+}
+
+.toggle-button .img {
+ vertical-align: middle;
+}
+
+.toggle-button.end {
+ margin-inline-end: 0px;
+ margin-inline-start: auto;
+}
+
+.toggle-button.start {
+ margin-inline-start: 0px;
+}
+
+html[dir="rtl"] .toggle-button.start .img,
+html[dir="ltr"] .toggle-button.end:not(.vertical) .img {
+ transform: scaleX(-1);
+}
+
+.toggle-button.end.vertical .img {
+ transform: rotate(-90deg);
+}
diff --git a/devtools/client/debugger/src/components/shared/Button/styles/moz.build b/devtools/client/debugger/src/components/shared/Button/styles/moz.build
new file mode 100644
index 0000000000..7d80140dbe
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/styles/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += []
+
+CompiledModules()
diff --git a/devtools/client/debugger/src/components/shared/Button/tests/CloseButton.spec.js b/devtools/client/debugger/src/components/shared/Button/tests/CloseButton.spec.js
new file mode 100644
index 0000000000..5e448881d9
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/tests/CloseButton.spec.js
@@ -0,0 +1,31 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+import { CloseButton } from "../";
+
+describe("CloseButton", () => {
+ it("renders with tooltip", () => {
+ const tooltip = "testTooltip";
+ const wrapper = shallow(
+ React.createElement(CloseButton, {
+ tooltip: tooltip,
+ handleClick: () => {},
+ })
+ );
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("handles click event", () => {
+ const handleClickSpy = jest.fn();
+ const wrapper = shallow(
+ React.createElement(CloseButton, {
+ handleClick: handleClickSpy,
+ })
+ );
+ wrapper.simulate("click");
+ expect(handleClickSpy).toHaveBeenCalled();
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/Button/tests/CommandBarButton.spec.js b/devtools/client/debugger/src/components/shared/Button/tests/CommandBarButton.spec.js
new file mode 100644
index 0000000000..41537cf8e4
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/tests/CommandBarButton.spec.js
@@ -0,0 +1,44 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+import { CommandBarButton, debugBtn } from "../";
+
+describe("CommandBarButton", () => {
+ it("renders", () => {
+ const wrapper = shallow(
+ React.createElement(CommandBarButton, {
+ children: [],
+ className: "",
+ })
+ );
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("renders children", () => {
+ const children = [1, 2, 3, 4];
+ const wrapper = shallow(
+ React.createElement(CommandBarButton, {
+ children: children,
+ className: "",
+ })
+ );
+ expect(wrapper.find("button").children()).toHaveLength(4);
+ });
+});
+
+describe("debugBtn", () => {
+ it("renders", () => {
+ const wrapper = shallow(debugBtn());
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("handles onClick", () => {
+ const onClickSpy = jest.fn();
+ const wrapper = shallow(debugBtn(onClickSpy));
+ wrapper.simulate("click");
+ expect(onClickSpy).toHaveBeenCalled();
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/Button/tests/PaneToggleButton.spec.js b/devtools/client/debugger/src/components/shared/Button/tests/PaneToggleButton.spec.js
new file mode 100644
index 0000000000..89b548379d
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/tests/PaneToggleButton.spec.js
@@ -0,0 +1,51 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+import { PaneToggleButton } from "../";
+
+describe("PaneToggleButton", () => {
+ const handleClickSpy = jest.fn();
+ const wrapper = shallow(
+ React.createElement(PaneToggleButton, {
+ handleClick: handleClickSpy,
+ collapsed: false,
+ position: "start",
+ })
+ );
+
+ it("renders default", () => {
+ expect(wrapper.hasClass("vertical")).toBe(true);
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("toggles horizontal class", () => {
+ wrapper.setProps({ horizontal: true });
+ expect(wrapper.hasClass("vertical")).toBe(false);
+ });
+
+ it("toggles collapsed class", () => {
+ wrapper.setProps({ collapsed: true });
+ expect(wrapper.hasClass("collapsed")).toBe(true);
+ });
+
+ it("toggles start position", () => {
+ wrapper.setProps({ position: "start" });
+ expect(wrapper.hasClass("start")).toBe(true);
+ });
+
+ it("toggles end position", () => {
+ wrapper.setProps({ position: "end" });
+ expect(wrapper.hasClass("end")).toBe(true);
+ });
+
+ it("handleClick is called", () => {
+ const position = "end";
+ const collapsed = false;
+ wrapper.setProps({ position, collapsed });
+ wrapper.simulate("click");
+ expect(handleClickSpy).toHaveBeenCalledWith(position, true);
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CloseButton.spec.js.snap b/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CloseButton.spec.js.snap
new file mode 100644
index 0000000000..d0a0cb9967
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CloseButton.spec.js.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CloseButton renders with tooltip 1`] = `
+<button
+ className="close-btn"
+ onClick={[Function]}
+ title="testTooltip"
+>
+ <AccessibleImage
+ className="close"
+ />
+</button>
+`;
diff --git a/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CommandBarButton.spec.js.snap b/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CommandBarButton.spec.js.snap
new file mode 100644
index 0000000000..cebcb5892c
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/CommandBarButton.spec.js.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CommandBarButton renders 1`] = `
+<button
+ aria-pressed={false}
+ className="command-bar-button"
+/>
+`;
+
+exports[`debugBtn renders 1`] = `
+<button
+ aria-pressed={false}
+ className="command-bar-button"
+ disabled={false}
+>
+ <AccessibleImage />
+</button>
+`;
diff --git a/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/PaneToggleButton.spec.js.snap b/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/PaneToggleButton.spec.js.snap
new file mode 100644
index 0000000000..86067066a6
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Button/tests/__snapshots__/PaneToggleButton.spec.js.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PaneToggleButton renders default 1`] = `
+<CommandBarButton
+ className="toggle-button start vertical"
+ onClick={[Function]}
+ title="Collapse Sources and Outline panes"
+>
+ <AccessibleImage
+ className="pane-collapse"
+ />
+</CommandBarButton>
+`;
diff --git a/devtools/client/debugger/src/components/shared/Dropdown.css b/devtools/client/debugger/src/components/shared/Dropdown.css
new file mode 100644
index 0000000000..bb9295b296
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Dropdown.css
@@ -0,0 +1,97 @@
+/* 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/>. */
+
+.dropdown {
+ background: var(--theme-body-background);
+ border: 1px solid var(--theme-splitter-color);
+ border-radius: 4px;
+ box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent);
+ max-height: 300px;
+ position: absolute;
+ top: 24px;
+ width: 150px;
+ z-index: 1000;
+ overflow: auto;
+}
+
+[dir="ltr"] .dropdown {
+ right: 2px;
+}
+
+[dir="rtl"] .dropdown {
+ left: 2px;
+}
+
+.dropdown-block {
+ position: relative;
+ align-self: center;
+ height: 100%;
+}
+
+/* cover the reserved space at the end of .source-tabs */
+.source-tabs + .dropdown-block {
+ margin-inline-start: -28px;
+}
+
+.dropdown-button {
+ color: var(--theme-comment);
+ background: none;
+ border: none;
+ padding: 4px 6px;
+ font-weight: 100;
+ font-size: 14px;
+ height: 100%;
+ width: 28px;
+ outline-offset: -2px;
+}
+
+.dropdown-button .img {
+ display: block;
+}
+
+.dropdown ul {
+ margin: 0;
+ padding: 4px 0;
+ list-style: none;
+}
+
+.dropdown li {
+ display: flex;
+ align-items: center;
+ padding: 6px 8px;
+ font-size: 12px;
+ line-height: calc(16 / 12);
+ transition: all 0.25s ease;
+}
+
+.dropdown li:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.dropdown-icon {
+ margin-inline-end: 4px;
+ mask-size: 13px 13px;
+}
+
+.dropdown-label {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dropdown-icon.prettyPrint,
+.dropdown-icon.blackBox {
+ background-color: var(--theme-highlight-blue);
+}
+
+.dropdown-mask {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ z-index: 999;
+ left: 0;
+ top: 0;
+}
diff --git a/devtools/client/debugger/src/components/shared/Dropdown.js b/devtools/client/debugger/src/components/shared/Dropdown.js
new file mode 100644
index 0000000000..a47eef9534
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Dropdown.js
@@ -0,0 +1,74 @@
+/* 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 { Component } from "devtools/client/shared/vendor/react";
+import { button, div } from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+export class Dropdown extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ dropdownShown: false,
+ };
+ }
+
+ static get propTypes() {
+ return {
+ icon: PropTypes.node.isRequired,
+ panel: PropTypes.node.isRequired,
+ };
+ }
+
+ toggleDropdown = e => {
+ this.setState(prevState => ({
+ dropdownShown: !prevState.dropdownShown,
+ }));
+ };
+
+ renderPanel() {
+ return div(
+ {
+ className: "dropdown",
+ onClick: this.toggleDropdown,
+ style: {
+ display: this.state.dropdownShown ? "block" : "none",
+ },
+ },
+ this.props.panel
+ );
+ }
+
+ renderButton() {
+ return button(
+ {
+ className: "dropdown-button",
+ onClick: this.toggleDropdown,
+ },
+ this.props.icon
+ );
+ }
+
+ renderMask() {
+ return div({
+ className: "dropdown-mask",
+ onClick: this.toggleDropdown,
+ style: {
+ display: this.state.dropdownShown ? "block" : "none",
+ },
+ });
+ }
+ render() {
+ return div(
+ {
+ className: "dropdown-block",
+ },
+ this.renderPanel(),
+ this.renderButton(),
+ this.renderMask()
+ );
+ }
+}
+
+export default Dropdown;
diff --git a/devtools/client/debugger/src/components/shared/Modal.css b/devtools/client/debugger/src/components/shared/Modal.css
new file mode 100644
index 0000000000..2c8f429285
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Modal.css
@@ -0,0 +1,47 @@
+/* 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/>. */
+
+.modal-wrapper {
+ position: fixed;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ z-index: 100;
+}
+
+.modal {
+ display: flex;
+ flex-direction: column;
+ /* Place the modal below the sources tab strip */
+ margin-block-start: var(--editor-header-height);
+ width: 80%;
+ max-height: 80vh;
+ overflow-y: auto;
+ background-color: var(--theme-toolbar-background);
+ box-shadow: 1px 1px 6px 1px var(--popup-shadow-color);
+
+ @media not (prefers-reduced-motion) {
+ animation: 150ms cubic-bezier(0.07, 0.95, 0, 1) slidein forwards;
+ }
+}
+
+@keyframes slidein {
+ from {
+ transform: translateY(-101%);
+ }
+ to {
+ transform: translateY(0);
+ }
+}
+
+/* This rule is active when the screen is not narrow */
+@media (min-width: 580px) {
+ .modal {
+ width: 50%;
+ }
+}
diff --git a/devtools/client/debugger/src/components/shared/Modal.js b/devtools/client/debugger/src/components/shared/Modal.js
new file mode 100644
index 0000000000..c14732f302
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Modal.js
@@ -0,0 +1,45 @@
+/* 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 PropTypes from "devtools/client/shared/vendor/react-prop-types";
+import React from "devtools/client/shared/vendor/react";
+import { div } from "devtools/client/shared/vendor/react-dom-factories";
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+class Modal extends React.Component {
+ static get propTypes() {
+ return {
+ additionalClass: PropTypes.string,
+ children: PropTypes.node.isRequired,
+ handleClose: PropTypes.func.isRequired,
+ };
+ }
+
+ onClick = e => {
+ e.stopPropagation();
+ };
+
+ render() {
+ const { additionalClass, children, handleClose } = this.props;
+ return div(
+ {
+ className: "modal-wrapper",
+ onClick: handleClose,
+ },
+ div(
+ {
+ className: classnames("modal", additionalClass),
+ onClick: this.onClick,
+ },
+ children
+ )
+ );
+ }
+}
+
+Modal.contextTypes = {
+ shortcuts: PropTypes.object,
+};
+
+export default Modal;
diff --git a/devtools/client/debugger/src/components/shared/Popover.css b/devtools/client/debugger/src/components/shared/Popover.css
new file mode 100644
index 0000000000..5da8ea4b63
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Popover.css
@@ -0,0 +1,32 @@
+/* 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/>. */
+
+.popover {
+ position: fixed;
+ z-index: 100;
+ --gap-size: 10px;
+ --left-offset: -55px;
+}
+
+.popover.orientation-right {
+ display: flex;
+ flex-direction: row;
+}
+
+.popover.orientation-right .gap {
+ width: var(--gap-size);
+}
+
+.popover:not(.orientation-right) .gap {
+ height: var(--gap-size);
+ margin-left: var(--left-offset);
+}
+
+.popover:not(.orientation-right) .preview-popup {
+ margin-left: var(--left-offset);
+}
+
+.popover .add-to-expression-bar {
+ margin-left: var(--left-offset);
+}
diff --git a/devtools/client/debugger/src/components/shared/Popover.js b/devtools/client/debugger/src/components/shared/Popover.js
new file mode 100644
index 0000000000..8748e36418
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/Popover.js
@@ -0,0 +1,324 @@
+/* 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 } from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+import BracketArrow from "./BracketArrow";
+import SmartGap from "./SmartGap";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+class Popover extends Component {
+ state = {
+ coords: {
+ left: 0,
+ top: 0,
+ orientation: "down",
+ targetMid: { x: 0, y: 0 },
+ },
+ };
+ firstRender = true;
+
+ static defaultProps = {
+ type: "popover",
+ };
+
+ static get propTypes() {
+ return {
+ children: PropTypes.node.isRequired,
+ editorRef: PropTypes.object.isRequired,
+ mouseout: PropTypes.func.isRequired,
+ target: PropTypes.object.isRequired,
+ targetPosition: PropTypes.object.isRequired,
+ type: PropTypes.string.isRequired,
+ };
+ }
+
+ componentDidMount() {
+ const { type } = this.props;
+ this.gapHeight = this.$gap.getBoundingClientRect().height;
+ const coords =
+ type == "popover" ? this.getPopoverCoords() : this.getTooltipCoords();
+
+ if (coords) {
+ this.setState({ coords });
+ }
+
+ this.firstRender = false;
+ this.startTimer();
+ }
+
+ componentDidUpdate(prevProps) {
+ // We have to update `coords` when the Popover type changes
+ if (
+ prevProps.type != this.props.type ||
+ prevProps.target !== this.props.target
+ ) {
+ const coords =
+ this.props.type == "popover"
+ ? this.getPopoverCoords()
+ : this.getTooltipCoords();
+
+ if (coords) {
+ this.setState({ coords });
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.timerId) {
+ clearTimeout(this.timerId);
+ }
+ }
+
+ startTimer() {
+ this.timerId = setTimeout(this.onTimeout, 0);
+ }
+
+ onTimeout = () => {
+ const isHoveredOnGap = this.$gap && this.$gap.matches(":hover");
+ const isHoveredOnPopover = this.$popover && this.$popover.matches(":hover");
+ const isHoveredOnTooltip = this.$tooltip && this.$tooltip.matches(":hover");
+ const isHoveredOnTarget = this.props.target.matches(":hover");
+
+ if (isHoveredOnGap) {
+ if (!this.wasOnGap) {
+ this.wasOnGap = true;
+ this.timerId = setTimeout(this.onTimeout, 200);
+ return;
+ }
+ this.props.mouseout();
+ return;
+ }
+
+ // Don't clear the current preview if mouse is hovered on
+ // the current preview's token (target) or the popup element
+ if (isHoveredOnPopover || isHoveredOnTooltip || isHoveredOnTarget) {
+ this.wasOnGap = false;
+ this.timerId = setTimeout(this.onTimeout, 0);
+ return;
+ }
+
+ this.props.mouseout();
+ };
+
+ calculateLeft(target, editor, popover, orientation) {
+ const estimatedLeft = target.left;
+ const estimatedRight = estimatedLeft + popover.width;
+ const isOverflowingRight = estimatedRight > editor.right;
+ if (orientation === "right") {
+ return target.left + target.width;
+ }
+ if (isOverflowingRight) {
+ const adjustedLeft = editor.right - popover.width - 8;
+ return adjustedLeft;
+ }
+ return estimatedLeft;
+ }
+
+ calculateTopForRightOrientation = (target, editor, popover) => {
+ if (popover.height <= editor.height) {
+ const rightOrientationTop = target.top - popover.height / 2;
+ if (rightOrientationTop < editor.top) {
+ return editor.top - target.height;
+ }
+ const rightOrientationBottom = rightOrientationTop + popover.height;
+ if (rightOrientationBottom > editor.bottom) {
+ return editor.bottom + target.height - popover.height + this.gapHeight;
+ }
+ return rightOrientationTop;
+ }
+ return editor.top - target.height;
+ };
+
+ calculateOrientation(target, editor, popover) {
+ const estimatedBottom = target.bottom + popover.height;
+ if (editor.bottom > estimatedBottom) {
+ return "down";
+ }
+ const upOrientationTop = target.top - popover.height;
+ if (upOrientationTop > editor.top) {
+ return "up";
+ }
+
+ return "right";
+ }
+
+ calculateTop = (target, editor, popover, orientation) => {
+ if (orientation === "down") {
+ return target.bottom;
+ }
+ if (orientation === "up") {
+ return target.top - popover.height;
+ }
+
+ return this.calculateTopForRightOrientation(target, editor, popover);
+ };
+
+ getPopoverCoords() {
+ if (!this.$popover || !this.props.editorRef) {
+ return null;
+ }
+
+ const popover = this.$popover;
+ const editor = this.props.editorRef;
+ const popoverRect = popover.getBoundingClientRect();
+ const editorRect = editor.getBoundingClientRect();
+ const targetRect = this.props.targetPosition;
+ const orientation = this.calculateOrientation(
+ targetRect,
+ editorRect,
+ popoverRect
+ );
+ const top = this.calculateTop(
+ targetRect,
+ editorRect,
+ popoverRect,
+ orientation
+ );
+ const popoverLeft = this.calculateLeft(
+ targetRect,
+ editorRect,
+ popoverRect,
+ orientation
+ );
+ let targetMid;
+ if (orientation === "right") {
+ targetMid = {
+ x: -14,
+ y: targetRect.top - top - 2,
+ };
+ } else {
+ targetMid = {
+ x: targetRect.left - popoverLeft + targetRect.width / 2 - 8,
+ y: 0,
+ };
+ }
+
+ return {
+ left: popoverLeft,
+ top,
+ orientation,
+ targetMid,
+ };
+ }
+
+ getTooltipCoords() {
+ if (!this.$tooltip || !this.props.editorRef) {
+ return null;
+ }
+ const tooltip = this.$tooltip;
+ const editor = this.props.editorRef;
+ const tooltipRect = tooltip.getBoundingClientRect();
+ const editorRect = editor.getBoundingClientRect();
+ const targetRect = this.props.targetPosition;
+ const left = this.calculateLeft(targetRect, editorRect, tooltipRect);
+ const enoughRoomForTooltipAbove =
+ targetRect.top - editorRect.top > tooltipRect.height;
+ const top = enoughRoomForTooltipAbove
+ ? targetRect.top - tooltipRect.height
+ : targetRect.bottom;
+
+ return {
+ left,
+ top,
+ orientation: enoughRoomForTooltipAbove ? "up" : "down",
+ targetMid: { x: 0, y: 0 },
+ };
+ }
+
+ getChildren() {
+ const { children } = this.props;
+ const { coords } = this.state;
+ const gap = this.getGap();
+
+ return coords.orientation === "up" ? [children, gap] : [gap, children];
+ }
+
+ getGap() {
+ if (this.firstRender) {
+ return div({
+ className: "gap",
+ key: "gap",
+ ref: a => (this.$gap = a),
+ });
+ }
+
+ return div(
+ {
+ className: "gap",
+ key: "gap",
+ ref: a => (this.$gap = a),
+ },
+ React.createElement(SmartGap, {
+ token: this.props.target,
+ preview: this.$tooltip || this.$popover,
+ type: this.props.type,
+ gapHeight: this.gapHeight,
+ coords: this.state.coords,
+ offset: this.$gap.getBoundingClientRect().left,
+ })
+ );
+ }
+
+ getPopoverArrow(orientation, left, top) {
+ let arrowProps = {};
+
+ if (orientation === "up") {
+ arrowProps = { orientation: "down", bottom: 10, left };
+ } else if (orientation === "down") {
+ arrowProps = { orientation: "up", top: -2, left };
+ } else {
+ arrowProps = { orientation: "left", top, left: -4 };
+ }
+ return React.createElement(BracketArrow, arrowProps);
+ }
+
+ renderPopover() {
+ const { top, left, orientation, targetMid } = this.state.coords;
+ const arrow = this.getPopoverArrow(orientation, targetMid.x, targetMid.y);
+ return div(
+ {
+ className: classnames("popover", `orientation-${orientation}`, {
+ up: orientation === "up",
+ }),
+ style: {
+ top,
+ left,
+ },
+ ref: c => (this.$popover = c),
+ },
+ arrow,
+ this.getChildren()
+ );
+ }
+
+ renderTooltip() {
+ const { top, left, orientation } = this.state.coords;
+ return div(
+ {
+ className: `tooltip orientation-${orientation}`,
+ style: {
+ top,
+ left,
+ },
+ ref: c => (this.$tooltip = c),
+ },
+ this.getChildren()
+ );
+ }
+
+ render() {
+ const { type } = this.props;
+
+ if (type === "tooltip") {
+ return this.renderTooltip();
+ }
+
+ return this.renderPopover();
+ }
+}
+
+export default Popover;
diff --git a/devtools/client/debugger/src/components/shared/PreviewFunction.css b/devtools/client/debugger/src/components/shared/PreviewFunction.css
new file mode 100644
index 0000000000..bff9ce25a2
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/PreviewFunction.css
@@ -0,0 +1,23 @@
+/* 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/>. */
+
+.function-signature {
+ align-self: center;
+}
+
+.function-signature .function-name {
+ color: var(--theme-highlight-blue);
+}
+
+.function-signature .param {
+ color: var(--theme-highlight-red);
+}
+
+.function-signature .paren {
+ color: var(--object-color);
+}
+
+.function-signature .comma {
+ color: var(--object-color);
+}
diff --git a/devtools/client/debugger/src/components/shared/PreviewFunction.js b/devtools/client/debugger/src/components/shared/PreviewFunction.js
new file mode 100644
index 0000000000..1a6d164cdf
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/PreviewFunction.js
@@ -0,0 +1,108 @@
+/* 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 { Component } from "devtools/client/shared/vendor/react";
+import {
+ span,
+ button,
+} from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+import { formatDisplayName } from "../../utils/pause/frames/index";
+
+const IGNORED_SOURCE_URLS = ["debugger eval code"];
+
+export default class PreviewFunction extends Component {
+ static get propTypes() {
+ return {
+ func: PropTypes.object.isRequired,
+ };
+ }
+
+ renderFunctionName(func) {
+ const { l10n } = this.context;
+ const name = formatDisplayName(func, undefined, l10n);
+ return span(
+ {
+ className: "function-name",
+ },
+ name
+ );
+ }
+
+ renderParams(func) {
+ const { parameterNames = [] } = func;
+
+ return parameterNames
+ .filter(Boolean)
+ .map((param, i, arr) => {
+ const elements = [
+ span(
+ {
+ className: "param",
+ key: param,
+ },
+ param
+ ),
+ ];
+ // if this isn't the last param, add a comma
+ if (i !== arr.length - 1) {
+ elements.push(
+ span(
+ {
+ className: "delimiter",
+ key: i,
+ },
+ ", "
+ )
+ );
+ }
+ return elements;
+ })
+ .flat();
+ }
+
+ jumpToDefinitionButton(func) {
+ const { location } = func;
+
+ if (!location?.url || IGNORED_SOURCE_URLS.includes(location.url)) {
+ return null;
+ }
+
+ const lastIndex = location.url.lastIndexOf("/");
+ return button({
+ className: "jump-definition",
+ draggable: "false",
+ title: `${location.url.slice(lastIndex + 1)}:${location.line}`,
+ });
+ }
+
+ render() {
+ const { func } = this.props;
+ return span(
+ {
+ className: "function-signature",
+ },
+ this.renderFunctionName(func),
+ span(
+ {
+ className: "paren",
+ },
+ "("
+ ),
+ this.renderParams(func),
+ span(
+ {
+ className: "paren",
+ },
+ ")"
+ ),
+ this.jumpToDefinitionButton(func)
+ );
+ }
+}
+
+PreviewFunction.contextTypes = {
+ l10n: PropTypes.object,
+};
diff --git a/devtools/client/debugger/src/components/shared/ResultList.css b/devtools/client/debugger/src/components/shared/ResultList.css
new file mode 100644
index 0000000000..037c3497d3
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/ResultList.css
@@ -0,0 +1,131 @@
+/* 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/>. */
+
+.result-list {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+ overflow: auto;
+ width: 100%;
+ background: var(--theme-body-background);
+}
+
+.result-list * {
+ user-select: none;
+}
+
+.result-list li {
+ color: var(--theme-body-color);
+ padding: 4px 8px;
+ display: flex;
+}
+
+.result-list.big li {
+ flex-direction: row;
+ align-items: center;
+ padding: 6px 8px;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+.result-list.small li {
+ justify-content: space-between;
+}
+
+.result-list li:hover {
+ background: var(--theme-tab-toolbar-background);
+}
+
+.theme-dark .result-list li:hover {
+ background: var(--grey-70);
+}
+
+.result-list li.selected {
+ background: var(--theme-accordion-header-background);
+}
+
+.result-list.small li.selected {
+ background-color: var(--theme-selection-background);
+ color: white;
+}
+
+.result-list li .result-item-icon {
+ background-color: var(--theme-icon-dimmed-color);
+}
+
+.result-list li .icon {
+ align-self: center;
+ margin-inline-end: 14px;
+ margin-inline-start: 4px;
+}
+
+.result-list .result-item-icon {
+ display: block;
+}
+
+.result-list .selected .result-item-icon {
+ background-color: var(--theme-selection-color);
+}
+
+.result-list li .title {
+ word-break: break-all;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ /** https://searchfox.org/mozilla-central/source/devtools/client/themes/variables.css **/
+ color: var(--grey-90);
+}
+
+.theme-dark .result-list li .title {
+ /** https://searchfox.org/mozilla-central/source/devtools/client/themes/variables.css **/
+ color: var(--grey-30);
+}
+
+.result-list li.selected .title {
+ color: white;
+}
+
+.result-list.big li.selected {
+ background-color: var(--theme-selection-background);
+ color: white;
+}
+
+.result-list.big li.selected .subtitle {
+ color: white;
+}
+
+.result-list.big li.selected .subtitle .highlight {
+ color: white;
+ font-weight: bold;
+}
+
+.result-list.big li .subtitle {
+ word-break: break-all;
+ /** https://searchfox.org/mozilla-central/source/devtools/client/themes/variables.css **/
+ color: var(--grey-40);
+ margin-left: 15px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.theme-dark .result-list.big li.selected .subtitle {
+ color: white;
+}
+
+.theme-dark .result-list.big li .subtitle {
+ color: var(--theme-text-color-inactive);
+}
+
+.search-bar .result-list li.selected .subtitle {
+ color: white;
+}
+
+.search-bar .result-list {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.theme-dark .result-list {
+ background-color: var(--theme-body-background);
+}
diff --git a/devtools/client/debugger/src/components/shared/ResultList.js b/devtools/client/debugger/src/components/shared/ResultList.js
new file mode 100644
index 0000000000..6b29de51f4
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/ResultList.js
@@ -0,0 +1,102 @@
+/* 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 { li, div, ul } from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+import AccessibleImage from "./AccessibleImage";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+import { scrollList } from "../../utils/result-list";
+
+export default class ResultList extends Component {
+ static defaultProps = {
+ size: "small",
+ role: "listbox",
+ };
+
+ static get propTypes() {
+ return {
+ items: PropTypes.array.isRequired,
+ role: PropTypes.oneOf(["listbox"]),
+ selectItem: PropTypes.func.isRequired,
+ selected: PropTypes.number.isRequired,
+ size: PropTypes.oneOf(["big", "small"]),
+ };
+ }
+
+ constructor(props) {
+ super(props);
+ this.ref = React.createRef();
+ }
+
+ componentDidUpdate() {
+ if (this.ref.current.childNodes) {
+ scrollList(this.ref.current.childNodes, this.props.selected);
+ }
+ }
+
+ renderListItem = (item, index) => {
+ if (item.value === "/" && item.title === "") {
+ item.title = "(index)";
+ }
+
+ const { selectItem, selected } = this.props;
+ const props = {
+ onClick: event => selectItem(event, item, index),
+ key: `${item.id}${item.value}${index}`,
+ title: item.value,
+ "aria-labelledby": `${item.id}-title`,
+ "aria-describedby": `${item.id}-subtitle`,
+ role: "option",
+ className: classnames("result-item", {
+ selected: index === selected,
+ }),
+ };
+
+ return li(
+ props,
+ item.icon &&
+ div(
+ {
+ className: "icon",
+ },
+ React.createElement(AccessibleImage, {
+ className: item.icon,
+ })
+ ),
+ div(
+ {
+ id: `${item.id}-title`,
+ className: "title",
+ },
+ item.title
+ ),
+ item.subtitle != item.title
+ ? div(
+ {
+ id: `${item.id}-subtitle`,
+ className: "subtitle",
+ },
+ item.subtitle
+ )
+ : null
+ );
+ };
+ render() {
+ const { size, items, role } = this.props;
+ return ul(
+ {
+ ref: this.ref,
+ className: classnames("result-list", size),
+ id: "result-list",
+ role: role,
+ "aria-live": "polite",
+ },
+ items.map(this.renderListItem)
+ );
+ }
+}
diff --git a/devtools/client/debugger/src/components/shared/SearchInput.css b/devtools/client/debugger/src/components/shared/SearchInput.css
new file mode 100644
index 0000000000..4a5ee85ed3
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/SearchInput.css
@@ -0,0 +1,223 @@
+/* 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/>. */
+
+.search-outline {
+ border: 1px solid var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ transition: border-color 200ms ease-in-out;
+ display: flex;
+ flex-direction: column;
+}
+
+.search-field {
+ position: relative;
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ min-height: 24px;
+ width: 100%;
+}
+
+.search-field .img.search {
+ --icon-mask-size: 12px;
+ --icon-inset-inline-start: 6px;
+ position: absolute;
+ z-index: 1;
+ top: calc(50% - 8px);
+ mask-size: var(--icon-mask-size);
+ background-color: var(--theme-icon-dimmed-color);
+ pointer-events: none;
+}
+
+.search-field.big .img.search {
+ --icon-mask-size: 16px;
+ --icon-inset-inline-start: 12px;
+}
+
+[dir="ltr"] .search-field .img.search {
+ left: var(--icon-inset-inline-start);
+}
+
+[dir="rtl"] .search-field .img.search {
+ right: var(--icon-inset-inline-start);
+}
+
+.search-field .img.loader {
+ width: 24px;
+ height: 24px;
+ margin-inline-end: 4px;
+}
+
+.search-field input {
+ align-self: stretch;
+ flex-grow: 1;
+ height: 24px;
+ width: 40px;
+ border: none;
+ padding: 4px;
+ padding-inline-start: 28px;
+ line-height: 16px;
+ font-family: inherit;
+ font-size: inherit;
+ color: var(--theme-body-color);
+ background-color: transparent;
+ outline-offset: -1px;
+
+ &:focus-visible {
+ /* Don't show the box-shadow focus indicator, only keep the outline, otherwise the
+ shadow overlap the first item in the result list */
+ box-shadow: none;
+ }
+}
+
+.exclude-patterns-field {
+ position: relative;
+ display: flex;
+ align-items: flex-start;
+ flex-direction: column;
+ flex-shrink: 0;
+ min-height: 24px;
+ width: 100%;
+ border-top: 1px solid var(--theme-splitter-color);
+ margin-top: 1px;
+ outline-offset: -1px;
+}
+
+.exclude-patterns-field label {
+ padding-inline-start: 8px;
+ padding-top: 5px;
+ padding-bottom: 3px;
+ align-self: stretch;
+ background-color: var(--theme-accordion-header-background);
+ font-size: 12px;
+}
+
+.exclude-patterns-field input {
+ align-self: stretch;
+ height: 24px;
+ border: none;
+ padding-top: 14px;
+ padding-bottom: 14px;
+ padding-inline-start: 10px;
+ line-height: 16px;
+ font-family: inherit;
+ font-size: inherit;
+ color: var(--theme-body-color);
+ background-color: transparent;
+ border-top: 1px solid var(--theme-splitter-color);
+ min-height: 24px;
+ outline-offset: -1px;
+}
+
+.exclude-patterns-field input::placeholder {
+ color: var(--theme-text-color-alt);
+ opacity: 1;
+}
+
+.search-field.big input {
+ height: 40px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ padding-inline-start: 40px;
+ font-size: 14px;
+ line-height: 20px;
+}
+
+.search-field input::placeholder {
+ color: var(--theme-text-color-alt);
+ opacity: 1;
+}
+
+.search-field-summary {
+ align-self: center;
+ padding: 2px 4px;
+ white-space: nowrap;
+ text-align: center;
+ user-select: none;
+ color: var(--theme-text-color-alt);
+ /* Avoid layout jumps when we increment the result count quickly. With tabular
+ numbers, layout will only jump between 9 and 10, 99 and 100, etc. */
+ font-variant-numeric: tabular-nums;
+}
+
+.search-field.big .search-field-summary {
+ margin-inline-end: 4px;
+}
+
+.search-field .search-nav-buttons {
+ display: flex;
+ user-select: none;
+}
+
+.search-field .search-nav-buttons .nav-btn {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ padding: 4px;
+ background: transparent;
+ outline-offset: -2px;
+}
+
+.search-field .search-nav-buttons .nav-btn:hover {
+ background-color: var(--theme-toolbar-background-hover);
+}
+
+.search-field .close-btn {
+ margin-inline-end: 4px;
+}
+
+.search-field.big .close-btn {
+ margin-inline-end: 8px;
+}
+
+.search-field .close-btn::-moz-focus-inner {
+ border: none;
+}
+
+.search-buttons-bar .pipe-divider {
+ flex: none;
+ align-self: stretch;
+ width: 1px;
+ vertical-align: middle;
+ margin: 4px;
+ background-color: var(--theme-splitter-color);
+}
+
+.search-buttons-bar * {
+ user-select: none;
+}
+
+.search-buttons-bar {
+ display: flex;
+ flex-shrink: 0;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 0;
+}
+
+.search-buttons-bar .search-type-toggles {
+ display: flex;
+ align-items: center;
+ max-width: 68%;
+}
+
+.search-buttons-bar .search-type-name {
+ margin: 0 4px;
+ border: none;
+ background: transparent;
+ color: var(--theme-comment);
+}
+
+.search-buttons-bar .search-type-toggles .search-type-btn.active {
+ color: var(--theme-selection-background);
+}
+
+.theme-dark .search-buttons-bar .search-type-toggles .search-type-btn.active {
+ color: white;
+}
+
+.search-buttons-bar .close-btn {
+ margin-inline-end: 3px;
+}
diff --git a/devtools/client/debugger/src/components/shared/SearchInput.js b/devtools/client/debugger/src/components/shared/SearchInput.js
new file mode 100644
index 0000000000..18f6ffbebb
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/SearchInput.js
@@ -0,0 +1,362 @@
+/* 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 {
+ button,
+ div,
+ label,
+ input,
+ 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 { CloseButton } from "./Button/index";
+
+import AccessibleImage from "./AccessibleImage";
+import actions from "../../actions/index";
+import { getSearchOptions } from "../../selectors/index";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+const SearchModifiers = require("resource://devtools/client/shared/components/SearchModifiers.js");
+
+const arrowBtn = (onClick, type, className, tooltip) => {
+ const props = {
+ className,
+ key: type,
+ onClick,
+ title: tooltip,
+ type,
+ };
+ return button(
+ props,
+ React.createElement(AccessibleImage, {
+ className: type,
+ })
+ );
+};
+
+export class SearchInput extends Component {
+ static defaultProps = {
+ expanded: false,
+ hasPrefix: false,
+ selectedItemId: "",
+ size: "",
+ showClose: true,
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ history: [],
+ excludePatterns: props.searchOptions.excludePatterns,
+ };
+ }
+
+ static get propTypes() {
+ return {
+ count: PropTypes.number.isRequired,
+ expanded: PropTypes.bool.isRequired,
+ handleClose: PropTypes.func,
+ handleNext: PropTypes.func,
+ handlePrev: PropTypes.func,
+ hasPrefix: PropTypes.bool.isRequired,
+ isLoading: PropTypes.bool.isRequired,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ onFocus: PropTypes.func,
+ onHistoryScroll: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onKeyUp: PropTypes.func,
+ placeholder: PropTypes.string,
+ query: PropTypes.string,
+ selectedItemId: PropTypes.string,
+ shouldFocus: PropTypes.bool,
+ showClose: PropTypes.bool.isRequired,
+ showExcludePatterns: PropTypes.bool.isRequired,
+ excludePatternsLabel: PropTypes.string,
+ excludePatternsPlaceholder: PropTypes.string,
+ showErrorEmoji: PropTypes.bool.isRequired,
+ size: PropTypes.string,
+ summaryMsg: PropTypes.string,
+ searchKey: PropTypes.string.isRequired,
+ searchOptions: PropTypes.object,
+ setSearchOptions: PropTypes.func,
+ showSearchModifiers: PropTypes.bool.isRequired,
+ onToggleSearchModifier: PropTypes.func,
+ };
+ }
+
+ componentDidMount() {
+ this.setFocus();
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.shouldFocus && !prevProps.shouldFocus) {
+ this.setFocus();
+ }
+ }
+
+ setFocus() {
+ if (this.$input) {
+ const _input = this.$input;
+ _input.focus();
+
+ if (!_input.value) {
+ return;
+ }
+
+ // omit prefix @:# from being selected
+ const selectStartPos = this.props.hasPrefix ? 1 : 0;
+ _input.setSelectionRange(selectStartPos, _input.value.length + 1);
+ }
+ }
+
+ renderArrowButtons() {
+ const { handleNext, handlePrev } = this.props;
+
+ return [
+ arrowBtn(
+ handlePrev,
+ "arrow-up",
+ classnames("nav-btn", "prev"),
+ L10N.getFormatStr("editor.searchResults.prevResult")
+ ),
+ arrowBtn(
+ handleNext,
+ "arrow-down",
+ classnames("nav-btn", "next"),
+ L10N.getFormatStr("editor.searchResults.nextResult")
+ ),
+ ];
+ }
+
+ onFocus = e => {
+ const { onFocus } = this.props;
+
+ if (onFocus) {
+ onFocus(e);
+ }
+ };
+
+ onBlur = e => {
+ const { onBlur } = this.props;
+
+ if (onBlur) {
+ onBlur(e);
+ }
+ };
+
+ onKeyDown = e => {
+ const { onHistoryScroll, onKeyDown } = this.props;
+ if (!onHistoryScroll) {
+ onKeyDown(e);
+ return;
+ }
+
+ const inputValue = e.target.value;
+ const { history } = this.state;
+ const currentHistoryIndex = history.indexOf(inputValue);
+
+ if (e.key === "Enter") {
+ this.saveEnteredTerm(inputValue);
+ onKeyDown(e);
+ return;
+ }
+
+ if (e.key === "ArrowUp") {
+ const previous =
+ currentHistoryIndex > -1 ? currentHistoryIndex - 1 : history.length - 1;
+ const previousInHistory = history[previous];
+ if (previousInHistory) {
+ e.preventDefault();
+ onHistoryScroll(previousInHistory);
+ }
+ return;
+ }
+
+ if (e.key === "ArrowDown") {
+ const next = currentHistoryIndex + 1;
+ const nextInHistory = history[next];
+ if (nextInHistory) {
+ onHistoryScroll(nextInHistory);
+ }
+ }
+ };
+
+ onExcludeKeyDown = e => {
+ if (e.key === "Enter") {
+ this.props.setSearchOptions(this.props.searchKey, {
+ excludePatterns: this.state.excludePatterns,
+ });
+ this.props.onKeyDown(e);
+ }
+ };
+
+ saveEnteredTerm(query) {
+ const { history } = this.state;
+ const previousIndex = history.indexOf(query);
+ if (previousIndex !== -1) {
+ history.splice(previousIndex, 1);
+ }
+ history.push(query);
+ this.setState({ history });
+ }
+
+ renderSummaryMsg() {
+ const { summaryMsg } = this.props;
+
+ if (!summaryMsg) {
+ return null;
+ }
+ return div(
+ {
+ className: "search-field-summary",
+ },
+ summaryMsg
+ );
+ }
+
+ renderSpinner() {
+ const { isLoading } = this.props;
+ if (!isLoading) {
+ return null;
+ }
+ return React.createElement(AccessibleImage, {
+ className: "loader spin",
+ });
+ }
+
+ renderNav() {
+ const { count, handleNext, handlePrev } = this.props;
+ if ((!handleNext && !handlePrev) || !count || count == 1) {
+ return null;
+ }
+ return div(
+ {
+ className: "search-nav-buttons",
+ },
+ this.renderArrowButtons()
+ );
+ }
+
+ renderSearchModifiers() {
+ if (!this.props.showSearchModifiers) {
+ return null;
+ }
+ return React.createElement(SearchModifiers, {
+ modifiers: this.props.searchOptions,
+ onToggleSearchModifier: updatedOptions => {
+ this.props.setSearchOptions(this.props.searchKey, updatedOptions);
+ this.props.onToggleSearchModifier();
+ },
+ });
+ }
+
+ renderExcludePatterns() {
+ if (!this.props.showExcludePatterns) {
+ return null;
+ }
+ return div(
+ {
+ className: classnames("exclude-patterns-field", this.props.size),
+ },
+ label(null, this.props.excludePatternsLabel),
+ input({
+ placeholder: this.props.excludePatternsPlaceholder,
+ value: this.state.excludePatterns,
+ onKeyDown: this.onExcludeKeyDown,
+ onChange: e =>
+ this.setState({
+ excludePatterns: e.target.value,
+ }),
+ })
+ );
+ }
+
+ renderClose() {
+ if (!this.props.showClose) {
+ return null;
+ }
+ return React.createElement(
+ React.Fragment,
+ null,
+ span({
+ className: "pipe-divider",
+ }),
+ React.createElement(CloseButton, {
+ handleClick: this.props.handleClose,
+ buttonClass: this.props.size,
+ })
+ );
+ }
+
+ render() {
+ const {
+ expanded,
+ onChange,
+ onKeyUp,
+ placeholder,
+ query,
+ selectedItemId,
+ showErrorEmoji,
+ size,
+ } = this.props;
+
+ const inputProps = {
+ className: classnames({
+ empty: showErrorEmoji,
+ }),
+ onChange,
+ onKeyDown: e => this.onKeyDown(e),
+ onKeyUp,
+ onFocus: e => this.onFocus(e),
+ onBlur: e => this.onBlur(e),
+ "aria-autocomplete": "list",
+ "aria-controls": "result-list",
+ "aria-activedescendant":
+ expanded && selectedItemId ? `${selectedItemId}-title` : "",
+ placeholder,
+ value: query,
+ spellCheck: false,
+ ref: c => (this.$input = c),
+ };
+ return div(
+ {
+ className: "search-outline",
+ },
+ div(
+ {
+ className: classnames("search-field", size),
+ role: "combobox",
+ "aria-haspopup": "listbox",
+ "aria-owns": "result-list",
+ "aria-expanded": expanded,
+ },
+ React.createElement(AccessibleImage, {
+ className: "search",
+ }),
+ input(inputProps),
+ this.renderSpinner(),
+ this.renderSummaryMsg(),
+ this.renderNav(),
+ div(
+ {
+ className: "search-buttons-bar",
+ },
+ this.renderSearchModifiers(),
+ this.renderClose()
+ )
+ ),
+ this.renderExcludePatterns()
+ );
+ }
+}
+const mapStateToProps = (state, props) => ({
+ searchOptions: getSearchOptions(state, props.searchKey),
+});
+
+export default connect(mapStateToProps, {
+ setSearchOptions: actions.setSearchOptions,
+})(SearchInput);
diff --git a/devtools/client/debugger/src/components/shared/SmartGap.js b/devtools/client/debugger/src/components/shared/SmartGap.js
new file mode 100644
index 0000000000..d76d018987
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/SmartGap.js
@@ -0,0 +1,170 @@
+/* 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 {
+ svg,
+ polygon,
+} from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+function shorten(coordinates) {
+ // In cases where the token is wider than the preview, the smartGap
+ // gets distorted. This shortens the coordinate array so that the smartGap
+ // is only touching 2 corners of the token (instead of all 4 corners)
+ coordinates.splice(0, 2);
+ coordinates.splice(4, 2);
+ return coordinates;
+}
+
+function getSmartGapCoordinates(
+ preview,
+ token,
+ offset,
+ orientation,
+ gapHeight,
+ coords
+) {
+ if (orientation === "up") {
+ const coordinates = [
+ token.left - coords.left + offset,
+ token.top + token.height - (coords.top + preview.height) + gapHeight,
+ 0,
+ 0,
+ preview.width + offset,
+ 0,
+ token.left + token.width - coords.left + offset,
+ token.top + token.height - (coords.top + preview.height) + gapHeight,
+ token.left + token.width - coords.left + offset,
+ token.top - (coords.top + preview.height) + gapHeight,
+ token.left - coords.left + offset,
+ token.top - (coords.top + preview.height) + gapHeight,
+ ];
+ return preview.width > token.width ? coordinates : shorten(coordinates);
+ }
+ if (orientation === "down") {
+ const coordinates = [
+ token.left + token.width - (coords.left + preview.top) + offset,
+ 0,
+ preview.width + offset,
+ coords.top - token.top + gapHeight,
+ 0,
+ coords.top - token.top + gapHeight,
+ token.left - (coords.left + preview.top) + offset,
+ 0,
+ token.left - (coords.left + preview.top) + offset,
+ token.height,
+ token.left + token.width - (coords.left + preview.top) + offset,
+ token.height,
+ ];
+ return preview.width > token.width ? coordinates : shorten(coordinates);
+ }
+ return [
+ 0,
+ token.top - coords.top,
+ gapHeight + token.width,
+ 0,
+ gapHeight + token.width,
+ preview.height - gapHeight,
+ 0,
+ token.top + token.height - coords.top,
+ token.width,
+ token.top + token.height - coords.top,
+ token.width,
+ token.top - coords.top,
+ ];
+}
+
+function getSmartGapDimensions(
+ previewRect,
+ tokenRect,
+ offset,
+ orientation,
+ gapHeight,
+ coords
+) {
+ if (orientation === "up") {
+ return {
+ height:
+ tokenRect.top +
+ tokenRect.height -
+ coords.top -
+ previewRect.height +
+ gapHeight,
+ width: Math.max(previewRect.width, tokenRect.width) + offset,
+ };
+ }
+ if (orientation === "down") {
+ return {
+ height: coords.top - tokenRect.top + gapHeight,
+ width: Math.max(previewRect.width, tokenRect.width) + offset,
+ };
+ }
+ return {
+ height: previewRect.height - gapHeight,
+ width: coords.left - tokenRect.left + gapHeight,
+ };
+}
+
+export default function SmartGap({
+ token,
+ preview,
+ type,
+ gapHeight,
+ coords,
+ offset,
+}) {
+ const tokenRect = token.getBoundingClientRect();
+ const previewRect = preview.getBoundingClientRect();
+ const { orientation } = coords;
+ let optionalMarginLeft, optionalMarginTop;
+
+ if (orientation === "down") {
+ optionalMarginTop = -tokenRect.height;
+ } else if (orientation === "right") {
+ optionalMarginLeft = -tokenRect.width;
+ }
+
+ const { height, width } = getSmartGapDimensions(
+ previewRect,
+ tokenRect,
+ -offset,
+ orientation,
+ gapHeight,
+ coords
+ );
+ const coordinates = getSmartGapCoordinates(
+ previewRect,
+ tokenRect,
+ -offset,
+ orientation,
+ gapHeight,
+ coords
+ );
+ return svg(
+ {
+ version: "1.1",
+ xmlns: "http://www.w3.org/2000/svg",
+ style: {
+ height,
+ width,
+ position: "absolute",
+ marginLeft: optionalMarginLeft,
+ marginTop: optionalMarginTop,
+ },
+ },
+ polygon({
+ points: coordinates,
+ fill: "transparent",
+ })
+ );
+}
+
+SmartGap.propTypes = {
+ coords: PropTypes.object.isRequired,
+ gapHeight: PropTypes.number.isRequired,
+ offset: PropTypes.number.isRequired,
+ preview: PropTypes.object.isRequired,
+ token: PropTypes.object.isRequired,
+ type: PropTypes.oneOf(["popover", "tooltip"]).isRequired,
+};
diff --git a/devtools/client/debugger/src/components/shared/SourceIcon.css b/devtools/client/debugger/src/components/shared/SourceIcon.css
new file mode 100644
index 0000000000..0b9bf3e79e
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/SourceIcon.css
@@ -0,0 +1,176 @@
+/* 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/>. */
+
+/**
+ * Variant of AccessibleImage used in sources list and tabs.
+ * Define the different source type / framework / library icons here.
+ */
+
+.source-icon {
+ margin-inline-end: 4px;
+}
+
+/* Icons for frameworks and libs */
+
+.img.aframe {
+ background-image: url(chrome://devtools/content/debugger/images/sources/aframe.svg);
+ background-color: transparent !important;
+}
+
+.img.angular {
+ background-image: url(chrome://devtools/content/debugger/images/sources/angular.svg);
+ background-color: transparent !important;
+}
+
+.img.babel {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/babel.svg);
+}
+
+.img.backbone {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/backbone.svg);
+}
+
+.img.choo {
+ background-image: url(chrome://devtools/content/debugger/images/sources/choo.svg);
+ background-color: transparent !important;
+}
+
+.img.coffeescript {
+ background-image: url(chrome://devtools/content/debugger/images/sources/coffeescript.svg);
+ background-color: transparent !important;
+ fill: var(--theme-icon-color);
+ -moz-context-properties: fill;
+}
+
+.img.dojo {
+ background-image: url(chrome://devtools/content/debugger/images/sources/dojo.svg);
+ background-color: transparent !important;
+}
+
+.img.ember {
+ background-image: url(chrome://devtools/content/debugger/images/sources/ember.svg);
+ background-color: transparent !important;
+}
+
+.img.express {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/express.svg);
+}
+
+.img.extension {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/extension.svg);
+}
+
+.img.immutable {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/immutable.svg);
+}
+
+.img.javascript {
+ background-image: url(chrome://devtools/content/debugger/images/sources/javascript.svg);
+ background-size: 14px 14px;
+ background-color: transparent !important;
+ fill: var(--theme-icon-color);
+ -moz-context-properties: fill;
+}
+
+.img.override::after {
+ content: "";
+ display: block;
+ height: 5px;
+ width: 5px;
+ background-color: var(--purple-30);
+ border-radius: 100%;
+ outline: 1px solid var(--theme-sidebar-background);
+ translate: 12px 10px;
+}
+
+.node.focused .img.override::after {
+ outline-color: var(--theme-selection-background);
+}
+
+.img.jquery {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/jquery.svg);
+}
+
+.img.lodash {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/lodash.svg);
+}
+
+.img.marko {
+ background-image: url(chrome://devtools/content/debugger/images/sources/marko.svg);
+ background-color: transparent !important;
+}
+
+.img.mobx {
+ background-image: url(chrome://devtools/content/debugger/images/sources/mobx.svg);
+ background-color: transparent !important;
+}
+
+.img.nextjs {
+ background-image: url(chrome://devtools/content/debugger/images/sources/nextjs.svg);
+ background-color: transparent !important;
+}
+
+.img.node {
+ background-image: url(chrome://devtools/content/debugger/images/sources/node.svg);
+ background-color: transparent !important;
+}
+
+.img.nuxtjs {
+ background-image: url(chrome://devtools/content/debugger/images/sources/nuxtjs.svg);
+ background-color: transparent !important;
+}
+
+.img.preact {
+ background-image: url(chrome://devtools/content/debugger/images/sources/preact.svg);
+ background-color: transparent !important;
+}
+
+.img.pug {
+ background-image: url(chrome://devtools/content/debugger/images/sources/pug.svg);
+ background-color: transparent !important;
+}
+
+.img.react {
+ background-image: url(chrome://devtools/content/debugger/images/sources/react.svg);
+ background-color: transparent !important;
+ fill: var(--theme-highlight-bluegrey);
+ -moz-context-properties: fill;
+}
+
+.img.redux {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/redux.svg);
+}
+
+.img.rxjs {
+ background-image: url(chrome://devtools/content/debugger/images/sources/rxjs.svg);
+ background-color: transparent !important;
+}
+
+.img.sencha-extjs {
+ background-image: url(chrome://devtools/content/debugger/images/sources/sencha-extjs.svg);
+ background-color: transparent !important;
+}
+
+.img.typescript {
+ background-image: url(chrome://devtools/content/debugger/images/sources/typescript.svg);
+ background-color: transparent !important;
+ fill: var(--theme-icon-color);
+ -moz-context-properties: fill;
+}
+
+.img.underscore {
+ mask-image: url(chrome://devtools/content/debugger/images/sources/underscore.svg);
+}
+
+/* We use both 'Vue' and 'VueJS' when identifying frameworks */
+.img.vue,
+.img.vuejs {
+ background-image: url(chrome://devtools/content/debugger/images/sources/vuejs.svg);
+ background-color: transparent !important;
+}
+
+.img.webpack {
+ background-image: url(chrome://devtools/content/debugger/images/sources/webpack.svg);
+ background-color: transparent !important;
+}
diff --git a/devtools/client/debugger/src/components/shared/SourceIcon.js b/devtools/client/debugger/src/components/shared/SourceIcon.js
new file mode 100644
index 0000000000..b2a7486bd6
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/SourceIcon.js
@@ -0,0 +1,71 @@
+/* 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, { PureComponent } from "devtools/client/shared/vendor/react";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+import { connect } from "devtools/client/shared/vendor/react-redux";
+
+import AccessibleImage from "./AccessibleImage";
+
+import { getSourceClassnames } from "../../utils/source";
+import {
+ getSymbols,
+ isSourceBlackBoxed,
+ hasPrettyTab,
+} from "../../selectors/index";
+
+class SourceIcon extends PureComponent {
+ static get propTypes() {
+ return {
+ modifier: PropTypes.func.isRequired,
+ location: PropTypes.object.isRequired,
+ iconClass: PropTypes.string,
+ forTab: PropTypes.bool,
+ };
+ }
+
+ render() {
+ const { modifier } = this.props;
+ let { iconClass } = this.props;
+
+ if (modifier) {
+ const modified = modifier(iconClass);
+ if (!modified) {
+ return null;
+ }
+ iconClass = modified;
+ }
+ return React.createElement(AccessibleImage, {
+ className: `source-icon ${iconClass}`,
+ });
+ }
+}
+
+export default connect((state, props) => {
+ const { forTab, location } = props;
+ // BreakpointHeading sometimes spawn locations without source actor for generated sources
+ // which disallows fetching symbols. In such race condition return the default icon.
+ // (this reproduces when running browser_dbg-breakpoints-popup.js)
+ if (!location.source.isOriginal && !location.sourceActor) {
+ return "file";
+ }
+ const symbols = getSymbols(state, location);
+ const isBlackBoxed = isSourceBlackBoxed(state, location.source);
+ // For the tab icon, we don't want to show the pretty icon for the non-pretty tab
+ const hasMatchingPrettyTab = !forTab && hasPrettyTab(state, location.source);
+
+ // This is the key function that will compute the icon type,
+ // In addition to the "modifier" implemented by each callsite.
+ const iconClass = getSourceClassnames(
+ location.source,
+ symbols,
+ isBlackBoxed,
+ hasMatchingPrettyTab
+ );
+
+ return {
+ iconClass,
+ };
+})(SourceIcon);
diff --git a/devtools/client/debugger/src/components/shared/menu.css b/devtools/client/debugger/src/components/shared/menu.css
new file mode 100644
index 0000000000..37dfbc2e8f
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/menu.css
@@ -0,0 +1,55 @@
+/* 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/>. */
+
+menupopup {
+ position: fixed;
+ z-index: 10000;
+ border: 1px solid #cccccc;
+ padding: 5px 0;
+ background: #f2f2f2;
+ border-radius: 5px;
+ color: #585858;
+ box-shadow: 0 0 4px 0 rgba(190, 190, 190, 0.8);
+ min-width: 130px;
+}
+
+menuitem {
+ display: block;
+ padding: 0 20px;
+ line-height: 20px;
+ font-weight: 500;
+ font-size: 13px;
+ user-select: none;
+}
+
+menuitem:hover {
+ background: #3780fb;
+ color: white;
+}
+
+menuitem[disabled="true"] {
+ color: #cccccc;
+}
+
+menuitem[disabled="true"]:hover {
+ background-color: transparent;
+ cursor: default;
+}
+
+menuseparator {
+ border-bottom: 1px solid #cacdd3;
+ width: 100%;
+ height: 5px;
+ display: block;
+ margin-bottom: 5px;
+}
+
+#contextmenu-mask.show {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 999;
+}
diff --git a/devtools/client/debugger/src/components/shared/moz.build b/devtools/client/debugger/src/components/shared/moz.build
new file mode 100644
index 0000000000..b30ea0ab4f
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/moz.build
@@ -0,0 +1,23 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+ "Button",
+]
+
+CompiledModules(
+ "AccessibleImage.js",
+ "Accordion.js",
+ "Badge.js",
+ "BracketArrow.js",
+ "Dropdown.js",
+ "Modal.js",
+ "Popover.js",
+ "PreviewFunction.js",
+ "ResultList.js",
+ "SearchInput.js",
+ "SourceIcon.js",
+ "SmartGap.js",
+)
diff --git a/devtools/client/debugger/src/components/shared/tests/Accordion.spec.js b/devtools/client/debugger/src/components/shared/tests/Accordion.spec.js
new file mode 100644
index 0000000000..cbe5ab12bf
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/Accordion.spec.js
@@ -0,0 +1,47 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+
+import Accordion from "../Accordion";
+
+describe("Accordion", () => {
+ const testItems = [
+ {
+ header: "Test Accordion Item 1",
+ id: "accordion-item-1",
+ className: "accordion-item-1",
+ component: React.createElement("div", null),
+ opened: false,
+ onToggle: jest.fn(),
+ },
+ {
+ header: "Test Accordion Item 2",
+ id: "accordion-item-2",
+ className: "accordion-item-2",
+ component: React.createElement("div", null),
+ buttons: React.createElement("button", null),
+ opened: false,
+ onToggle: jest.fn(),
+ },
+ {
+ header: "Test Accordion Item 3",
+ id: "accordion-item-3",
+ className: "accordion-item-3",
+ component: React.createElement("div", null),
+ opened: true,
+ onToggle: jest.fn(),
+ },
+ ];
+ const wrapper = shallow(
+ React.createElement(Accordion, {
+ items: testItems,
+ })
+ );
+ it("basic render", () => expect(wrapper).toMatchSnapshot());
+ wrapper.find(".accordion-item-1 button").simulate("click");
+ it("handleClick and onToggle", () =>
+ expect(testItems[0].onToggle).toHaveBeenCalledWith(true));
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/Badge.spec.js b/devtools/client/debugger/src/components/shared/tests/Badge.spec.js
new file mode 100644
index 0000000000..a19b35a7c2
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/Badge.spec.js
@@ -0,0 +1,19 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+
+import Badge from "../Badge";
+
+describe("Badge", () => {
+ it("render", () =>
+ expect(
+ shallow(
+ React.createElement(Badge, {
+ badgeText: 3,
+ })
+ )
+ ).toMatchSnapshot());
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/BracketArrow.spec.js b/devtools/client/debugger/src/components/shared/tests/BracketArrow.spec.js
new file mode 100644
index 0000000000..4ce9a5b5ce
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/BracketArrow.spec.js
@@ -0,0 +1,24 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+
+import BracketArrow from "../BracketArrow";
+
+describe("BracketArrow", () => {
+ const wrapper = shallow(
+ React.createElement(BracketArrow, {
+ orientation: "down",
+ left: 10,
+ top: 20,
+ bottom: 50,
+ })
+ );
+ it("render", () => expect(wrapper).toMatchSnapshot());
+ it("render up", () => {
+ wrapper.setProps({ orientation: null });
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/Dropdown.spec.js b/devtools/client/debugger/src/components/shared/tests/Dropdown.spec.js
new file mode 100644
index 0000000000..9b001ba9e5
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/Dropdown.spec.js
@@ -0,0 +1,21 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+
+import Dropdown from "../Dropdown";
+
+describe("Dropdown", () => {
+ const wrapper = shallow(
+ React.createElement(Dropdown, {
+ panel: React.createElement("div", null),
+ icon: "✅",
+ })
+ );
+ it("render", () => expect(wrapper).toMatchSnapshot());
+ wrapper.find(".dropdown").simulate("click");
+ it("handle toggleDropdown", () =>
+ expect(wrapper.state().dropdownShown).toEqual(true));
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/Modal.spec.js b/devtools/client/debugger/src/components/shared/tests/Modal.spec.js
new file mode 100644
index 0000000000..58c38502e7
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/Modal.spec.js
@@ -0,0 +1,56 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+
+import Modal from "../Modal";
+
+describe("Modal", () => {
+ it("renders", () => {
+ const wrapper = shallow(
+ React.createElement(Modal, {
+ handleClose: () => {},
+ })
+ );
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("handles close modal click", () => {
+ const handleCloseSpy = jest.fn();
+ const wrapper = shallow(
+ React.createElement(Modal, {
+ handleClose: handleCloseSpy,
+ })
+ );
+ wrapper.find(".modal-wrapper").simulate("click");
+ expect(handleCloseSpy).toHaveBeenCalled();
+ });
+
+ it("renders children", () => {
+ const wrapper = shallow(
+ React.createElement(
+ Modal,
+ {
+ handleClose: () => {},
+ },
+ React.createElement("div", {
+ className: "aChild",
+ })
+ )
+ );
+ expect(wrapper.find(".aChild")).toHaveLength(1);
+ });
+
+ it("passes additionalClass to child div class", () => {
+ const additionalClass = "testAddon";
+ const wrapper = shallow(
+ React.createElement(Modal, {
+ additionalClass,
+ handleClose: () => {},
+ })
+ );
+ expect(wrapper.find(`.modal-wrapper .${additionalClass}`)).toHaveLength(1);
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/Popover.spec.js b/devtools/client/debugger/src/components/shared/tests/Popover.spec.js
new file mode 100644
index 0000000000..7150f4afe8
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/Popover.spec.js
@@ -0,0 +1,212 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { mount } from "enzyme";
+
+import Popover from "../Popover";
+
+describe("Popover", () => {
+ const onMouseLeave = jest.fn();
+ const onKeyDown = jest.fn();
+ const editorRef = {
+ getBoundingClientRect() {
+ return {
+ x: 0,
+ y: 0,
+ width: 100,
+ height: 100,
+ top: 250,
+ right: 0,
+ bottom: 0,
+ left: 20,
+ };
+ },
+ };
+
+ const targetRef = {
+ getBoundingClientRect() {
+ return {
+ x: 0,
+ y: 0,
+ width: 100,
+ height: 100,
+ top: 250,
+ right: 0,
+ bottom: 0,
+ left: 20,
+ };
+ },
+ };
+ const targetPosition = {
+ x: 100,
+ y: 200,
+ width: 300,
+ height: 300,
+ top: 50,
+ right: 0,
+ bottom: 0,
+ left: 200,
+ };
+ const popover = mount(
+ React.createElement(
+ Popover,
+ {
+ onMouseLeave: onMouseLeave,
+ onKeyDown: onKeyDown,
+ editorRef: editorRef,
+ targetPosition: targetPosition,
+ mouseout: () => {},
+ target: targetRef,
+ },
+ React.createElement("h1", null, "Poppy!")
+ )
+ );
+
+ const tooltip = mount(
+ React.createElement(
+ Popover,
+ {
+ type: "tooltip",
+ onMouseLeave: onMouseLeave,
+ onKeyDown: onKeyDown,
+ editorRef: editorRef,
+ targetPosition: targetPosition,
+ mouseout: () => {},
+ target: targetRef,
+ },
+ React.createElement("h1", null, "Toolie!")
+ )
+ );
+
+ beforeEach(() => {
+ onMouseLeave.mockClear();
+ onKeyDown.mockClear();
+ });
+
+ it("render", () => expect(popover).toMatchSnapshot());
+
+ it("render (tooltip)", () => expect(tooltip).toMatchSnapshot());
+
+ it("mount popover", () => {
+ const mountedPopover = mount(
+ React.createElement(
+ Popover,
+ {
+ onMouseLeave: onMouseLeave,
+ onKeyDown: onKeyDown,
+ editorRef: editorRef,
+ targetPosition: targetPosition,
+ mouseout: () => {},
+ target: targetRef,
+ },
+ React.createElement("h1", null, "Poppy!")
+ )
+ );
+ expect(mountedPopover).toMatchSnapshot();
+ });
+
+ it("mount tooltip", () => {
+ const mountedTooltip = mount(
+ React.createElement(
+ Popover,
+ {
+ type: "tooltip",
+ onMouseLeave: onMouseLeave,
+ onKeyDown: onKeyDown,
+ editorRef: editorRef,
+ targetPosition: targetPosition,
+ mouseout: () => {},
+ target: targetRef,
+ },
+ React.createElement("h1", null, "Toolie!")
+ )
+ );
+ expect(mountedTooltip).toMatchSnapshot();
+ });
+
+ it("tooltip normally displays above the target", () => {
+ const editor = {
+ getBoundingClientRect() {
+ return {
+ width: 500,
+ height: 500,
+ top: 0,
+ bottom: 500,
+ left: 0,
+ right: 500,
+ };
+ },
+ };
+ const target = {
+ width: 30,
+ height: 10,
+ top: 100,
+ bottom: 110,
+ left: 20,
+ right: 50,
+ };
+
+ const mountedTooltip = mount(
+ React.createElement(
+ Popover,
+ {
+ type: "tooltip",
+ onMouseLeave: onMouseLeave,
+ onKeyDown: onKeyDown,
+ editorRef: editor,
+ targetPosition: target,
+ mouseout: () => {},
+ target: targetRef,
+ },
+ React.createElement("h1", null, "Toolie!")
+ )
+ );
+
+ const toolTipTop = parseInt(mountedTooltip.getDOMNode().style.top, 10);
+ expect(toolTipTop).toBeLessThanOrEqual(target.top);
+ });
+
+ it("tooltop won't display above the target when insufficient space", () => {
+ const editor = {
+ getBoundingClientRect() {
+ return {
+ width: 100,
+ height: 100,
+ top: 0,
+ bottom: 100,
+ left: 0,
+ right: 100,
+ };
+ },
+ };
+ const target = {
+ width: 30,
+ height: 10,
+ top: 0,
+ bottom: 10,
+ left: 20,
+ right: 50,
+ };
+
+ const mountedTooltip = mount(
+ React.createElement(
+ Popover,
+ {
+ type: "tooltip",
+ onMouseLeave: onMouseLeave,
+ onKeyDown: onKeyDown,
+ editorRef: editor,
+ targetPosition: target,
+ mouseout: () => {},
+ target: targetRef,
+ },
+ React.createElement("h1", null, "Toolie!")
+ )
+ );
+
+ const toolTipTop = parseInt(mountedTooltip.getDOMNode().style.top, 10);
+ expect(toolTipTop).toBeGreaterThanOrEqual(target.bottom);
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/PreviewFunction.spec.js b/devtools/client/debugger/src/components/shared/tests/PreviewFunction.spec.js
new file mode 100644
index 0000000000..62f635acc1
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/PreviewFunction.spec.js
@@ -0,0 +1,130 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+import PreviewFunction from "../PreviewFunction";
+
+function render(props) {
+ return shallow(React.createElement(PreviewFunction, props), {
+ context: {
+ l10n: L10N,
+ },
+ });
+}
+
+describe("PreviewFunction", () => {
+ it("should return a span", () => {
+ const item = { name: "" };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan).toMatchSnapshot();
+ expect(returnedSpan.name()).toEqual("span");
+ });
+
+ it('should return a span with a class of "function-signature"', () => {
+ const item = { name: "" };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan.hasClass("function-signature")).toBe(true);
+ });
+
+ it("should return a span with 3 children", () => {
+ const item = { name: "" };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan.children()).toHaveLength(3);
+ });
+
+ describe("function name", () => {
+ it("should be a span", () => {
+ const item = { name: "" };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan.children().first().name()).toEqual("span");
+ });
+
+ it('should have a "function-name" class', () => {
+ const item = { name: "" };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan.children().first().hasClass("function-name")).toBe(
+ true
+ );
+ });
+
+ it("should be be set to userDisplayName if defined", () => {
+ const item = {
+ name: "",
+ displayName: "chuck",
+ };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan.children().first().first().text()).toEqual("chuck");
+ });
+
+ it('should use displayName if defined & no "userDisplayName" exist', () => {
+ const item = {
+ displayName: "norris",
+ name: "last",
+ };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan.children().first().first().text()).toEqual("norris");
+ });
+
+ it('should use to name if no "userDisplayName"/"displayName" exist', () => {
+ const item = {
+ name: "last",
+ };
+ const returnedSpan = render({ func: item });
+ expect(returnedSpan.children().first().first().text()).toEqual("last");
+ });
+ });
+
+ describe("render parentheses", () => {
+ let leftParen;
+ let rightParen;
+
+ beforeAll(() => {
+ const item = { name: "" };
+ const returnedSpan = render({ func: item });
+ const children = returnedSpan.children();
+ leftParen = returnedSpan.childAt(1);
+ rightParen = returnedSpan.childAt(children.length - 1);
+ });
+
+ it("should be spans", () => {
+ expect(leftParen.name()).toEqual("span");
+ expect(rightParen.name()).toEqual("span");
+ });
+
+ it("should create a left paren", () => {
+ expect(leftParen.text()).toEqual("(");
+ });
+
+ it("should create a right paren", () => {
+ expect(rightParen.text()).toEqual(")");
+ });
+ });
+
+ describe("render parameters", () => {
+ let returnedSpan;
+ let children;
+
+ beforeAll(() => {
+ const item = {
+ name: "",
+ parameterNames: ["one", "two", "three"],
+ };
+ returnedSpan = render({ func: item });
+ children = returnedSpan.children();
+ });
+
+ it("should render spans according to the dynamic params given", () => {
+ expect(children).toHaveLength(8);
+ });
+
+ it("should render the parameters names", () => {
+ expect(returnedSpan.childAt(2).text()).toEqual("one");
+ });
+
+ it("should render the parameters commas", () => {
+ expect(returnedSpan.childAt(3).text()).toEqual(", ");
+ });
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/ResultList.spec.js b/devtools/client/debugger/src/components/shared/tests/ResultList.spec.js
new file mode 100644
index 0000000000..4cdc85fb23
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/ResultList.spec.js
@@ -0,0 +1,48 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+import ResultList from "../ResultList";
+
+const selectItem = jest.fn();
+const selectedIndex = 1;
+const payload = {
+ items: [
+ {
+ id: 0,
+ subtitle: "subtitle",
+ title: "title",
+ value: "value",
+ },
+ {
+ id: 1,
+ subtitle: "subtitle 1",
+ title: "title 1",
+ value: "value 1",
+ },
+ ],
+ selected: selectedIndex,
+ selectItem,
+};
+
+describe("Result list", () => {
+ it("should call onClick function", () => {
+ const wrapper = shallow(React.createElement(ResultList, payload));
+ wrapper.childAt(selectedIndex).simulate("click");
+ expect(selectItem).toHaveBeenCalled();
+ });
+
+ it("should render the component", () => {
+ const wrapper = shallow(React.createElement(ResultList, payload));
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("selected index should have 'selected class'", () => {
+ const wrapper = shallow(React.createElement(ResultList, payload));
+ const childHasClass = wrapper.childAt(selectedIndex).hasClass("selected");
+
+ expect(childHasClass).toEqual(true);
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/SearchInput.spec.js b/devtools/client/debugger/src/components/shared/tests/SearchInput.spec.js
new file mode 100644
index 0000000000..c4c3990771
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/SearchInput.spec.js
@@ -0,0 +1,126 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+import configureStore from "redux-mock-store";
+
+import SearchInput from "../SearchInput";
+
+describe("SearchInput", () => {
+ // !! wrapper is defined outside test scope
+ // so it will keep values between tests
+ const mockStore = configureStore([]);
+ const store = mockStore({
+ ui: { mutableSearchOptions: { "foo-search": {} } },
+ });
+ const wrapper = shallow(
+ React.createElement(SearchInput, {
+ store: store,
+ query: "",
+ count: 5,
+ placeholder: "A placeholder",
+ summaryMsg: "So many results",
+ showErrorEmoji: false,
+ isLoading: false,
+ onChange: () => {},
+ onKeyDown: () => {},
+ searchKey: "foo-search",
+ showSearchModifiers: false,
+ showExcludePatterns: false,
+ showClose: true,
+ handleClose: jest.fn(),
+ setSearchOptions: jest.fn(),
+ })
+ ).dive();
+
+ it("renders", () => expect(wrapper).toMatchSnapshot());
+
+ it("shows nav buttons", () => {
+ wrapper.setProps({
+ handleNext: jest.fn(),
+ handlePrev: jest.fn(),
+ });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("shows svg error emoji", () => {
+ wrapper.setProps({ showErrorEmoji: true });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it("shows svg magnifying glass", () => {
+ wrapper.setProps({ showErrorEmoji: false });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ describe("with optional onHistoryScroll", () => {
+ const searches = ["foo", "bar", "baz"];
+ const createSearch = term => ({
+ target: { value: term },
+ key: "Enter",
+ });
+
+ const scrollUp = currentTerm => ({
+ key: "ArrowUp",
+ target: { value: currentTerm },
+ preventDefault: jest.fn(),
+ });
+ const scrollDown = currentTerm => ({
+ key: "ArrowDown",
+ target: { value: currentTerm },
+ preventDefault: jest.fn(),
+ });
+
+ it("stores entered history in state", () => {
+ wrapper.setProps({
+ onHistoryScroll: jest.fn(),
+ onKeyDown: jest.fn(),
+ });
+ wrapper.find("input").simulate("keyDown", createSearch(searches[0]));
+ expect(wrapper.state().history[0]).toEqual(searches[0]);
+ });
+
+ it("stores scroll history in state", () => {
+ const onHistoryScroll = jest.fn();
+ wrapper.setProps({
+ onHistoryScroll,
+ onKeyDown: jest.fn(),
+ });
+ wrapper.find("input").simulate("keyDown", createSearch(searches[0]));
+ wrapper.find("input").simulate("keyDown", createSearch(searches[1]));
+ expect(wrapper.state().history[0]).toEqual(searches[0]);
+ expect(wrapper.state().history[1]).toEqual(searches[1]);
+ });
+
+ it("scrolls up stored history on arrow up", () => {
+ const onHistoryScroll = jest.fn();
+ wrapper.setProps({
+ onHistoryScroll,
+ onKeyDown: jest.fn(),
+ });
+ wrapper.find("input").simulate("keyDown", createSearch(searches[0]));
+ wrapper.find("input").simulate("keyDown", createSearch(searches[1]));
+ wrapper.find("input").simulate("keyDown", scrollUp(searches[1]));
+ expect(wrapper.state().history[0]).toEqual(searches[0]);
+ expect(wrapper.state().history[1]).toEqual(searches[1]);
+ expect(onHistoryScroll).toHaveBeenCalledWith(searches[0]);
+ });
+
+ it("scrolls down stored history on arrow down", () => {
+ const onHistoryScroll = jest.fn();
+ wrapper.setProps({
+ onHistoryScroll,
+ onKeyDown: jest.fn(),
+ });
+ wrapper.find("input").simulate("keyDown", createSearch(searches[0]));
+ wrapper.find("input").simulate("keyDown", createSearch(searches[1]));
+ wrapper.find("input").simulate("keyDown", createSearch(searches[2]));
+ wrapper.find("input").simulate("keyDown", scrollUp(searches[2]));
+ wrapper.find("input").simulate("keyDown", scrollUp(searches[1]));
+ wrapper.find("input").simulate("keyDown", scrollDown(searches[0]));
+ expect(onHistoryScroll.mock.calls[2][0]).toBe(searches[1]);
+ });
+ });
+});
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/Accordion.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Accordion.spec.js.snap
new file mode 100644
index 0000000000..abd8f10f51
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Accordion.spec.js.snap
@@ -0,0 +1,81 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Accordion basic render 1`] = `
+<div
+ className="accordion"
+>
+ <aside
+ aria-labelledby="accordion-item-1"
+ className="accordion-item-1"
+ key="accordion-item-1"
+ >
+ <h2
+ className="_header"
+ >
+ <button
+ aria-controls="accordion-item-1-content"
+ aria-expanded="true"
+ className="header-label"
+ id="accordion-item-1"
+ onClick={[Function]}
+ >
+ Test Accordion Item 1
+ </button>
+ </h2>
+ <div
+ className="_content"
+ id="accordion-item-1-content"
+ >
+ <div />
+ </div>
+ </aside>
+ <aside
+ aria-labelledby="accordion-item-2"
+ className="accordion-item-2"
+ key="accordion-item-2"
+ >
+ <h2
+ className="_header"
+ >
+ <button
+ aria-expanded="false"
+ className="header-label"
+ id="accordion-item-2"
+ onClick={[Function]}
+ >
+ Test Accordion Item 2
+ </button>
+ <div
+ className="header-buttons"
+ >
+ <button />
+ </div>
+ </h2>
+ </aside>
+ <aside
+ aria-labelledby="accordion-item-3"
+ className="accordion-item-3"
+ key="accordion-item-3"
+ >
+ <h2
+ className="_header"
+ >
+ <button
+ aria-controls="accordion-item-3-content"
+ aria-expanded="true"
+ className="header-label"
+ id="accordion-item-3"
+ onClick={[Function]}
+ >
+ Test Accordion Item 3
+ </button>
+ </h2>
+ <div
+ className="_content"
+ id="accordion-item-3-content"
+ >
+ <div />
+ </div>
+ </aside>
+</div>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/Badge.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Badge.spec.js.snap
new file mode 100644
index 0000000000..cbeeeaa3f2
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Badge.spec.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Badge render 1`] = `
+<span
+ className="badge text-white text-center"
+>
+ 3
+</span>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/BracketArrow.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/BracketArrow.spec.js.snap
new file mode 100644
index 0000000000..5078cebc9e
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/BracketArrow.spec.js.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`BracketArrow render 1`] = `
+<div
+ className="bracket-arrow down"
+ style={
+ Object {
+ "bottom": 50,
+ "left": 10,
+ "top": 20,
+ }
+ }
+/>
+`;
+
+exports[`BracketArrow render up 1`] = `
+<div
+ className="bracket-arrow up"
+ style={
+ Object {
+ "bottom": 50,
+ "left": 10,
+ "top": 20,
+ }
+ }
+/>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/Dropdown.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Dropdown.spec.js.snap
new file mode 100644
index 0000000000..fd60784327
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Dropdown.spec.js.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Dropdown render 1`] = `
+<div
+ className="dropdown-block"
+>
+ <div
+ className="dropdown"
+ onClick={[Function]}
+ style={
+ Object {
+ "display": "block",
+ }
+ }
+ >
+ <div />
+ </div>
+ <button
+ className="dropdown-button"
+ onClick={[Function]}
+ >
+ ✅
+ </button>
+ <div
+ className="dropdown-mask"
+ onClick={[Function]}
+ style={
+ Object {
+ "display": "block",
+ }
+ }
+ />
+</div>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/Modal.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Modal.spec.js.snap
new file mode 100644
index 0000000000..c8534c4032
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Modal.spec.js.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Modal renders 1`] = `
+<div
+ className="modal-wrapper"
+ onClick={[Function]}
+>
+ <div
+ className="modal"
+ onClick={[Function]}
+ />
+</div>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/Popover.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Popover.spec.js.snap
new file mode 100644
index 0000000000..1c3589a6f8
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/Popover.spec.js.snap
@@ -0,0 +1,549 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Popover mount popover 1`] = `
+<Popover
+ editorRef={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ mouseout={[Function]}
+ onKeyDown={[MockFunction]}
+ onMouseLeave={[MockFunction]}
+ target={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ targetPosition={
+ Object {
+ "bottom": 0,
+ "height": 300,
+ "left": 200,
+ "right": 0,
+ "top": 50,
+ "width": 300,
+ "x": 100,
+ "y": 200,
+ }
+ }
+ type="popover"
+>
+ <div
+ className="popover orientation-right"
+ style={
+ Object {
+ "left": 500,
+ "top": -50,
+ }
+ }
+ >
+ <BracketArrow
+ left={-4}
+ orientation="left"
+ top={98}
+ >
+ <div
+ className="bracket-arrow left"
+ style={
+ Object {
+ "bottom": undefined,
+ "left": -4,
+ "top": 98,
+ }
+ }
+ />
+ </BracketArrow>
+ <div
+ className="gap"
+ key="gap"
+ >
+ <SmartGap
+ coords={
+ Object {
+ "left": 500,
+ "orientation": "right",
+ "targetMid": Object {
+ "x": -14,
+ "y": 98,
+ },
+ "top": -50,
+ }
+ }
+ gapHeight={0}
+ offset={0}
+ preview={
+ <div
+ class="popover orientation-right"
+ style="top: -50px; left: 500px;"
+ >
+ <div
+ class="bracket-arrow left"
+ style="left: -4px; top: 98px;"
+ />
+ <div
+ class="gap"
+ >
+ <svg
+ style="height: 0px; width: 480px; position: absolute; margin-left: -100px;"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points="0,300,100,0,100,0,0,400,100,400,100,300"
+ />
+ </svg>
+ </div>
+ <h1>
+ Poppy!
+ </h1>
+ </div>
+ }
+ token={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ type="popover"
+ >
+ <svg
+ style={
+ Object {
+ "height": 0,
+ "marginLeft": -100,
+ "marginTop": undefined,
+ "position": "absolute",
+ "width": 480,
+ }
+ }
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points={
+ Array [
+ 0,
+ 300,
+ 100,
+ 0,
+ 100,
+ 0,
+ 0,
+ 400,
+ 100,
+ 400,
+ 100,
+ 300,
+ ]
+ }
+ />
+ </svg>
+ </SmartGap>
+ </div>
+ <h1>
+ Poppy!
+ </h1>
+ </div>
+</Popover>
+`;
+
+exports[`Popover mount tooltip 1`] = `
+<Popover
+ editorRef={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ mouseout={[Function]}
+ onKeyDown={[MockFunction]}
+ onMouseLeave={[MockFunction]}
+ target={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ targetPosition={
+ Object {
+ "bottom": 0,
+ "height": 300,
+ "left": 200,
+ "right": 0,
+ "top": 50,
+ "width": 300,
+ "x": 100,
+ "y": 200,
+ }
+ }
+ type="tooltip"
+>
+ <div
+ className="tooltip orientation-down"
+ style={
+ Object {
+ "left": -8,
+ "top": 0,
+ }
+ }
+ >
+ <div
+ className="gap"
+ key="gap"
+ >
+ <SmartGap
+ coords={
+ Object {
+ "left": -8,
+ "orientation": "down",
+ "targetMid": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "top": 0,
+ }
+ }
+ gapHeight={0}
+ offset={0}
+ preview={
+ <div
+ class="tooltip orientation-down"
+ style="top: 0px; left: -8px;"
+ >
+ <div
+ class="gap"
+ >
+ <svg
+ style="height: -250px; width: 100px; position: absolute; margin-top: -100px;"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points="0,-250,0,-250,28,100,128,100"
+ />
+ </svg>
+ </div>
+ <h1>
+ Toolie!
+ </h1>
+ </div>
+ }
+ token={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ type="tooltip"
+ >
+ <svg
+ style={
+ Object {
+ "height": -250,
+ "marginLeft": undefined,
+ "marginTop": -100,
+ "position": "absolute",
+ "width": 100,
+ }
+ }
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points={
+ Array [
+ 0,
+ -250,
+ 0,
+ -250,
+ 28,
+ 100,
+ 128,
+ 100,
+ ]
+ }
+ />
+ </svg>
+ </SmartGap>
+ </div>
+ <h1>
+ Toolie!
+ </h1>
+ </div>
+</Popover>
+`;
+
+exports[`Popover render (tooltip) 1`] = `
+<Popover
+ editorRef={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ mouseout={[Function]}
+ onKeyDown={[MockFunction]}
+ onMouseLeave={[MockFunction]}
+ target={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ targetPosition={
+ Object {
+ "bottom": 0,
+ "height": 300,
+ "left": 200,
+ "right": 0,
+ "top": 50,
+ "width": 300,
+ "x": 100,
+ "y": 200,
+ }
+ }
+ type="tooltip"
+>
+ <div
+ className="tooltip orientation-down"
+ style={
+ Object {
+ "left": -8,
+ "top": 0,
+ }
+ }
+ >
+ <div
+ className="gap"
+ key="gap"
+ >
+ <SmartGap
+ coords={
+ Object {
+ "left": -8,
+ "orientation": "down",
+ "targetMid": Object {
+ "x": 0,
+ "y": 0,
+ },
+ "top": 0,
+ }
+ }
+ gapHeight={0}
+ offset={0}
+ preview={
+ <div
+ class="tooltip orientation-down"
+ style="top: 0px; left: -8px;"
+ >
+ <div
+ class="gap"
+ >
+ <svg
+ style="height: -250px; width: 100px; position: absolute; margin-top: -100px;"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points="0,-250,0,-250,28,100,128,100"
+ />
+ </svg>
+ </div>
+ <h1>
+ Toolie!
+ </h1>
+ </div>
+ }
+ token={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ type="tooltip"
+ >
+ <svg
+ style={
+ Object {
+ "height": -250,
+ "marginLeft": undefined,
+ "marginTop": -100,
+ "position": "absolute",
+ "width": 100,
+ }
+ }
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points={
+ Array [
+ 0,
+ -250,
+ 0,
+ -250,
+ 28,
+ 100,
+ 128,
+ 100,
+ ]
+ }
+ />
+ </svg>
+ </SmartGap>
+ </div>
+ <h1>
+ Toolie!
+ </h1>
+ </div>
+</Popover>
+`;
+
+exports[`Popover render 1`] = `
+<Popover
+ editorRef={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ mouseout={[Function]}
+ onKeyDown={[MockFunction]}
+ onMouseLeave={[MockFunction]}
+ target={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ targetPosition={
+ Object {
+ "bottom": 0,
+ "height": 300,
+ "left": 200,
+ "right": 0,
+ "top": 50,
+ "width": 300,
+ "x": 100,
+ "y": 200,
+ }
+ }
+ type="popover"
+>
+ <div
+ className="popover orientation-right"
+ style={
+ Object {
+ "left": 500,
+ "top": -50,
+ }
+ }
+ >
+ <BracketArrow
+ left={-4}
+ orientation="left"
+ top={98}
+ >
+ <div
+ className="bracket-arrow left"
+ style={
+ Object {
+ "bottom": undefined,
+ "left": -4,
+ "top": 98,
+ }
+ }
+ />
+ </BracketArrow>
+ <div
+ className="gap"
+ key="gap"
+ >
+ <SmartGap
+ coords={
+ Object {
+ "left": 500,
+ "orientation": "right",
+ "targetMid": Object {
+ "x": -14,
+ "y": 98,
+ },
+ "top": -50,
+ }
+ }
+ gapHeight={0}
+ offset={0}
+ preview={
+ <div
+ class="popover orientation-right"
+ style="top: -50px; left: 500px;"
+ >
+ <div
+ class="bracket-arrow left"
+ style="left: -4px; top: 98px;"
+ />
+ <div
+ class="gap"
+ >
+ <svg
+ style="height: 0px; width: 480px; position: absolute; margin-left: -100px;"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points="0,300,100,0,100,0,0,400,100,400,100,300"
+ />
+ </svg>
+ </div>
+ <h1>
+ Poppy!
+ </h1>
+ </div>
+ }
+ token={
+ Object {
+ "getBoundingClientRect": [Function],
+ }
+ }
+ type="popover"
+ >
+ <svg
+ style={
+ Object {
+ "height": 0,
+ "marginLeft": -100,
+ "marginTop": undefined,
+ "position": "absolute",
+ "width": 480,
+ }
+ }
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <polygon
+ fill="transparent"
+ points={
+ Array [
+ 0,
+ 300,
+ 100,
+ 0,
+ 100,
+ 0,
+ 0,
+ 400,
+ 100,
+ 400,
+ 100,
+ 300,
+ ]
+ }
+ />
+ </svg>
+ </SmartGap>
+ </div>
+ <h1>
+ Poppy!
+ </h1>
+ </div>
+</Popover>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/PreviewFunction.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/PreviewFunction.spec.js.snap
new file mode 100644
index 0000000000..e766bd45aa
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/PreviewFunction.spec.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PreviewFunction should return a span 1`] = `
+<span
+ className="function-signature"
+>
+ <span
+ className="function-name"
+ >
+ &lt;anonymous&gt;
+ </span>
+ <span
+ className="paren"
+ >
+ (
+ </span>
+ <span
+ className="paren"
+ >
+ )
+ </span>
+</span>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/ResultList.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/ResultList.spec.js.snap
new file mode 100644
index 0000000000..d3d8b27575
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/ResultList.spec.js.snap
@@ -0,0 +1,55 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Result list should render the component 1`] = `
+<ul
+ aria-live="polite"
+ className="result-list small"
+ id="result-list"
+ role="listbox"
+>
+ <li
+ aria-describedby="0-subtitle"
+ aria-labelledby="0-title"
+ className="result-item"
+ key="0value0"
+ onClick={[Function]}
+ role="option"
+ title="value"
+ >
+ <div
+ className="title"
+ id="0-title"
+ >
+ title
+ </div>
+ <div
+ className="subtitle"
+ id="0-subtitle"
+ >
+ subtitle
+ </div>
+ </li>
+ <li
+ aria-describedby="1-subtitle"
+ aria-labelledby="1-title"
+ className="result-item selected"
+ key="1value 11"
+ onClick={[Function]}
+ role="option"
+ title="value 1"
+ >
+ <div
+ className="title"
+ id="1-title"
+ >
+ title 1
+ </div>
+ <div
+ className="subtitle"
+ id="1-subtitle"
+ >
+ subtitle 1
+ </div>
+ </li>
+</ul>
+`;
diff --git a/devtools/client/debugger/src/components/shared/tests/__snapshots__/SearchInput.spec.js.snap b/devtools/client/debugger/src/components/shared/tests/__snapshots__/SearchInput.spec.js.snap
new file mode 100644
index 0000000000..c56a13dc3b
--- /dev/null
+++ b/devtools/client/debugger/src/components/shared/tests/__snapshots__/SearchInput.spec.js.snap
@@ -0,0 +1,267 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SearchInput renders 1`] = `
+<div
+ className="search-outline"
+>
+ <div
+ aria-expanded={false}
+ aria-haspopup="listbox"
+ aria-owns="result-list"
+ className="search-field"
+ role="combobox"
+ >
+ <AccessibleImage
+ className="search"
+ />
+ <input
+ aria-activedescendant=""
+ aria-autocomplete="list"
+ aria-controls="result-list"
+ className=""
+ onBlur={[Function]}
+ onChange={[Function]}
+ onFocus={[Function]}
+ onKeyDown={[Function]}
+ placeholder="A placeholder"
+ spellCheck={false}
+ value=""
+ />
+ <div
+ className="search-field-summary"
+ >
+ So many results
+ </div>
+ <div
+ className="search-buttons-bar"
+ >
+ <span
+ className="pipe-divider"
+ />
+ <CloseButton
+ buttonClass=""
+ handleClick={[MockFunction]}
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`SearchInput shows nav buttons 1`] = `
+<div
+ className="search-outline"
+>
+ <div
+ aria-expanded={false}
+ aria-haspopup="listbox"
+ aria-owns="result-list"
+ className="search-field"
+ role="combobox"
+ >
+ <AccessibleImage
+ className="search"
+ />
+ <input
+ aria-activedescendant=""
+ aria-autocomplete="list"
+ aria-controls="result-list"
+ className=""
+ onBlur={[Function]}
+ onChange={[Function]}
+ onFocus={[Function]}
+ onKeyDown={[Function]}
+ placeholder="A placeholder"
+ spellCheck={false}
+ value=""
+ />
+ <div
+ className="search-field-summary"
+ >
+ So many results
+ </div>
+ <div
+ className="search-nav-buttons"
+ >
+ <button
+ className="nav-btn prev"
+ key="arrow-up"
+ onClick={[MockFunction]}
+ title="Previous result"
+ type="arrow-up"
+ >
+ <AccessibleImage
+ className="arrow-up"
+ />
+ </button>
+ <button
+ className="nav-btn next"
+ key="arrow-down"
+ onClick={[MockFunction]}
+ title="Next result"
+ type="arrow-down"
+ >
+ <AccessibleImage
+ className="arrow-down"
+ />
+ </button>
+ </div>
+ <div
+ className="search-buttons-bar"
+ >
+ <span
+ className="pipe-divider"
+ />
+ <CloseButton
+ buttonClass=""
+ handleClick={[MockFunction]}
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`SearchInput shows svg error emoji 1`] = `
+<div
+ className="search-outline"
+>
+ <div
+ aria-expanded={false}
+ aria-haspopup="listbox"
+ aria-owns="result-list"
+ className="search-field"
+ role="combobox"
+ >
+ <AccessibleImage
+ className="search"
+ />
+ <input
+ aria-activedescendant=""
+ aria-autocomplete="list"
+ aria-controls="result-list"
+ className="empty"
+ onBlur={[Function]}
+ onChange={[Function]}
+ onFocus={[Function]}
+ onKeyDown={[Function]}
+ placeholder="A placeholder"
+ spellCheck={false}
+ value=""
+ />
+ <div
+ className="search-field-summary"
+ >
+ So many results
+ </div>
+ <div
+ className="search-nav-buttons"
+ >
+ <button
+ className="nav-btn prev"
+ key="arrow-up"
+ onClick={[MockFunction]}
+ title="Previous result"
+ type="arrow-up"
+ >
+ <AccessibleImage
+ className="arrow-up"
+ />
+ </button>
+ <button
+ className="nav-btn next"
+ key="arrow-down"
+ onClick={[MockFunction]}
+ title="Next result"
+ type="arrow-down"
+ >
+ <AccessibleImage
+ className="arrow-down"
+ />
+ </button>
+ </div>
+ <div
+ className="search-buttons-bar"
+ >
+ <span
+ className="pipe-divider"
+ />
+ <CloseButton
+ buttonClass=""
+ handleClick={[MockFunction]}
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`SearchInput shows svg magnifying glass 1`] = `
+<div
+ className="search-outline"
+>
+ <div
+ aria-expanded={false}
+ aria-haspopup="listbox"
+ aria-owns="result-list"
+ className="search-field"
+ role="combobox"
+ >
+ <AccessibleImage
+ className="search"
+ />
+ <input
+ aria-activedescendant=""
+ aria-autocomplete="list"
+ aria-controls="result-list"
+ className=""
+ onBlur={[Function]}
+ onChange={[Function]}
+ onFocus={[Function]}
+ onKeyDown={[Function]}
+ placeholder="A placeholder"
+ spellCheck={false}
+ value=""
+ />
+ <div
+ className="search-field-summary"
+ >
+ So many results
+ </div>
+ <div
+ className="search-nav-buttons"
+ >
+ <button
+ className="nav-btn prev"
+ key="arrow-up"
+ onClick={[MockFunction]}
+ title="Previous result"
+ type="arrow-up"
+ >
+ <AccessibleImage
+ className="arrow-up"
+ />
+ </button>
+ <button
+ className="nav-btn next"
+ key="arrow-down"
+ onClick={[MockFunction]}
+ title="Next result"
+ type="arrow-down"
+ >
+ <AccessibleImage
+ className="arrow-down"
+ />
+ </button>
+ </div>
+ <div
+ className="search-buttons-bar"
+ >
+ <span
+ className="pipe-divider"
+ />
+ <CloseButton
+ buttonClass=""
+ handleClick={[MockFunction]}
+ />
+ </div>
+ </div>
+</div>
+`;