Bug 1520957 - [release 119] Fixes #7656 - Remove absolute positioning in directory view of sources pane (#7691). r=dwalsh
☠☠ backed out by 5b1c54cbac38 ☠ ☠
authorChristian Stuff <christianstuff90@gmail.com>
Fri, 18 Jan 2019 12:09:08 -0500
changeset 454573 6a62f14cbf219e30bb546410fda9c540307bb14d
parent 454572 ea1cf6443c80c01aaa99d8d11d143bdd24dc2a3d
child 454574 118720bcf7bd6cece9570eb8b459bc7d57cc227f
push id35400
push usercsabou@mozilla.com
push dateSat, 19 Jan 2019 09:59:33 +0000
treeherdermozilla-central@f90bab5af97e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdwalsh
bugs1520957
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1520957 - [release 119] Fixes #7656 - Remove absolute positioning in directory view of sources pane (#7691). r=dwalsh
devtools/client/debugger/new/dist/debugger.css
devtools/client/debugger/new/src/components/PrimaryPanes/Sources.css
devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js
devtools/client/debugger/new/src/components/PrimaryPanes/index.js
devtools/client/debugger/new/src/components/PrimaryPanes/tests/PrimaryPanes.spec.js
devtools/client/debugger/new/src/components/PrimaryPanes/tests/SourcesTree.spec.js
devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/PrimaryPanes.spec.js.snap
devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -2047,19 +2047,16 @@ menuseparator {
 
 .sources-clear-root {
   padding: 4px 3px 4px 3px;
   width: 100%;
   text-align: start;
   white-space: nowrap;
   color: inherit;
   display: block;
-  position: absolute;
-  top: 0;
-  left: 0;
   border-bottom: 1px solid var(--theme-splitter-color);
 }
 
 .sources-clear-root i {
   margin-right: 5px;
   position: relative;
 }
 
@@ -2086,17 +2083,16 @@ menuseparator {
 .sources-clear-root-label {
   margin-left: 5px;
 }
 
 .sources-pane {
   display: flex;
   flex: 1;
   flex-direction: column;
-  height: 100%;
 }
 
 .sources-list {
   flex: 1;
   display: flex;
 }
 
 .sources-list .managed-tree {
@@ -2232,16 +2228,20 @@ menuseparator {
   flex: 1;
   overflow: auto;
 }
 
 .source-outline-panel.has-root > div {
   height: 100%;
 }
 
+.source-outline-panel.has-root .thread-header {
+  margin-top: 4px;
+}
+
 .sources-list .managed-tree .tree .node .img.blackBox {
   mask: url("resource://devtools/client/debugger/new/images/blackBox.svg") no-repeat;
   mask-size: 100%;
   background-color: var(--theme-highlight-blue);
   width: 13px;
   height: 13px;
   display: inline-block;
   margin-inline-end: 6px;
@@ -2260,17 +2260,16 @@ menuseparator {
   display: block;
   position: relative;
 }
 
 .sources-list-custom-root .sources-pane {
   display: block;
 }
 
-.sources-list-custom-root .sources-list,
 .sources-list-custom-root .no-sources-message {
   position: absolute;
   top: 26px;
   right: 0;
   bottom: 0;
   left: 0;
 }
 
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/Sources.css
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/Sources.css
@@ -18,19 +18,16 @@
 
 .sources-clear-root {
   padding: 4px 3px 4px 3px;
   width: 100%;
   text-align: start;
   white-space: nowrap;
   color: inherit;
   display: block;
-  position: absolute;
-  top: 0;
-  left: 0;
   border-bottom: 1px solid var(--theme-splitter-color);
 }
 
 .sources-clear-root i {
   margin-right: 5px;
   position: relative;
 }
 
@@ -57,17 +54,16 @@
 .sources-clear-root-label {
   margin-left: 5px;
 }
 
 .sources-pane {
   display: flex;
   flex: 1;
   flex-direction: column;
-  height: 100%;
 }
 
 .sources-list {
   flex: 1;
   display: flex;
 }
 
 .sources-list .managed-tree {
@@ -203,16 +199,20 @@
   flex: 1;
   overflow: auto;
 }
 
 .source-outline-panel.has-root > div {
   height: 100%;
 }
 
+.source-outline-panel.has-root .thread-header {
+  margin-top: 4px;
+}
+
 .sources-list .managed-tree .tree .node .img.blackBox {
   mask: url(/images/blackBox.svg) no-repeat;
   mask-size: 100%;
   background-color: var(--theme-highlight-blue);
   width: 13px;
   height: 13px;
   display: inline-block;
   margin-inline-end: 6px;
@@ -231,17 +231,16 @@
   display: block;
   position: relative;
 }
 
 .sources-list-custom-root .sources-pane {
   display: block;
 }
 
-.sources-list-custom-root .sources-list,
 .sources-list-custom-root .no-sources-message {
   position: absolute;
   top: 26px;
   right: 0;
   bottom: 0;
   left: 0;
 }
 
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js
@@ -27,17 +27,16 @@ import { getGeneratedSourceByURL } from 
 
 // Actions
 import actions from "../../actions";
 
 // Components
 import AccessibleImage from "../shared/AccessibleImage";
 import SourcesTreeItem from "./SourcesTreeItem";
 import ManagedTree from "../shared/ManagedTree";
-import Svg from "../shared/Svg";
 
 // Utils
 import {
   createTree,
   getDirectories,
   isDirectory,
   getSourceFromNode,
   nodeHasChildren,
@@ -64,17 +63,16 @@ type Props = {
   sourceCount: number,
   shownSource?: Source,
   selectedSource?: Source,
   debuggeeUrl: string,
   projectRoot: string,
   expanded: Set<string>,
   selectSource: typeof actions.selectSource,
   setExpandedState: typeof actions.setExpandedState,
-  clearProjectDirectoryRoot: typeof actions.clearProjectDirectoryRoot,
   focusItem: typeof actions.focusItem,
   focused: TreeNode,
   workerCount: number
 };
 
 type State = {
   parentMap: ParentMap,
   sourceTree: TreeDirectory,
@@ -211,50 +209,26 @@ class SourcesTree extends Component<Prop
   renderEmptyElement(message) {
     return (
       <div key="empty" className="no-sources-message">
         {message}
       </div>
     );
   }
 
-  renderProjectRootHeader() {
-    const { projectRoot } = this.props;
-
-    if (!projectRoot) {
-      return null;
-    }
-
-    const rootLabel = projectRoot.split("/").pop();
-
-    return (
-      <div key="root" className="sources-clear-root-container">
-        <button
-          className="sources-clear-root"
-          onClick={() => this.props.clearProjectDirectoryRoot()}
-          title={L10N.getStr("removeDirectoryRoot.label")}
-        >
-          <Svg name="home" />
-          <Svg name="breadcrumb" />
-          <span className="sources-clear-root-label">{rootLabel}</span>
-        </button>
-      </div>
-    );
-  }
-
   getRoots = () => {
     const { projectRoot } = this.props;
     const { sourceTree } = this.state;
 
     const sourceContents = sourceTree.contents[0];
     const rootLabel = projectRoot.split("/").pop();
 
     // The "sourceTree.contents[0]" check ensures that there are contents
     // A custom root with no existing sources will be ignored
-    if (projectRoot) {
+    if (projectRoot && sourceContents) {
       if (sourceContents && sourceContents.name !== rootLabel) {
         return sourceContents.contents[0].contents;
       }
       return sourceContents.contents;
     }
 
     return sourceTree.contents;
   };
@@ -346,38 +320,24 @@ class SourcesTree extends Component<Prop
       <div className="node thread-header">
         <AccessibleImage className={"file"} />
         <span className="label">{L10N.getStr("mainThread")}</span>
       </div>
     );
   }
 
   render() {
-    const { projectRoot, worker } = this.props;
+    const { worker } = this.props;
 
     if (!features.windowlessWorkers && worker) {
       return null;
     }
 
-    if (this.isEmpty()) {
-      if (projectRoot) {
-        return this.renderPane(
-          this.renderProjectRootHeader(),
-          this.renderEmptyElement(L10N.getStr("sources.noSourcesAvailableRoot"))
-        );
-      }
-
-      return this.renderPane(
-        this.renderEmptyElement(L10N.getStr("sources.noSourcesAvailable"))
-      );
-    }
-
     return this.renderPane(
       this.renderThreadHeader(),
-      this.renderProjectRootHeader(),
       <div key="tree" className="sources-list" onKeyDown={this.onKeyDown}>
         {this.renderTree()}
       </div>
     );
   }
 }
 
 function getSourceForTree(
@@ -416,12 +376,11 @@ const mapStateToProps = (state, props) =
   };
 };
 
 export default connect(
   mapStateToProps,
   {
     selectSource: actions.selectSource,
     setExpandedState: actions.setExpandedState,
-    clearProjectDirectoryRoot: actions.clearProjectDirectoryRoot,
     focusItem: actions.focusItem
   }
 )(SourcesTree);
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/index.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/index.js
@@ -22,31 +22,34 @@ import { formatKeyShortcut } from "../..
 
 import Outline from "./Outline";
 import SourcesTree from "./SourcesTree";
 
 import type { SourcesMapByThread } from "../../reducers/types";
 import type { SelectedPrimaryPaneTabType } from "../../selectors";
 import type { Thread } from "../../types";
 
+import Svg from "../shared/Svg";
+
 import "./Sources.css";
 
 type State = {
   alphabetizeOutline: boolean
 };
 
 type Props = {
   selectedTab: SelectedPrimaryPaneTabType,
   sources: SourcesMapByThread,
   horizontal: boolean,
   projectRoot: string,
   sourceSearchOn: boolean,
   setPrimaryPaneTab: typeof actions.setPrimaryPaneTab,
   setActiveSearch: typeof actions.setActiveSearch,
   closeActiveSearch: typeof actions.closeActiveSearch,
+  clearProjectDirectoryRoot: typeof actions.clearProjectDirectoryRoot,
   threads: Thread[]
 };
 
 class PrimaryPanes extends Component<Props, State> {
   constructor(props: Props) {
     super(props);
 
     this.state = {
@@ -93,16 +96,40 @@ class PrimaryPanes extends Component<Pro
         className={classnames("tab outline-tab", { active: isOutline })}
         key="outline-tab"
       >
         {outline}
       </Tab>
     ];
   }
 
+  renderProjectRootHeader() {
+    const { projectRoot } = this.props;
+
+    if (!projectRoot) {
+      return null;
+    }
+
+    const rootLabel = projectRoot.split("/").pop();
+
+    return (
+      <div key="root" className="sources-clear-root-container">
+        <button
+          className="sources-clear-root"
+          onClick={() => this.props.clearProjectDirectoryRoot()}
+          title={L10N.getStr("removeDirectoryRoot.label")}
+        >
+          <Svg name="home" />
+          <Svg name="breadcrumb" />
+          <span className="sources-clear-root-label">{rootLabel}</span>
+        </button>
+      </div>
+    );
+  }
+
   renderThreadSources() {
     return this.props.threads.map(({ actor }) => (
       <SourcesTree thread={actor} key={actor} />
     ));
   }
 
   render() {
     const { selectedTab, projectRoot } = this.props;
@@ -118,17 +145,20 @@ class PrimaryPanes extends Component<Pro
           {this.renderOutlineTabs()}
         </TabList>
         <TabPanels
           className={classnames("source-outline-panel", {
             "has-root": projectRoot
           })}
           hasFocusableContent
         >
-          <div>{this.renderThreadSources()}</div>
+          <div>
+            {this.renderProjectRootHeader()}
+            {this.renderThreadSources()}
+          </div>
           <Outline
             alphabetizeOutline={this.state.alphabetizeOutline}
             onAlphabetizeClick={this.onAlphabetizeClick}
           />
         </TabPanels>
       </Tabs>
     );
   }
@@ -142,13 +172,14 @@ const mapStateToProps = state => ({
   projectRoot: getProjectDirectoryRoot(state)
 });
 
 const connector = connect(
   mapStateToProps,
   {
     setPrimaryPaneTab: actions.setPrimaryPaneTab,
     setActiveSearch: actions.setActiveSearch,
-    closeActiveSearch: actions.closeActiveSearch
+    closeActiveSearch: actions.closeActiveSearch,
+    clearProjectDirectoryRoot: actions.clearProjectDirectoryRoot
   }
 );
 
 export default connector(PrimaryPanes);
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/PrimaryPanes.spec.js
@@ -0,0 +1,72 @@
+/* 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 "react";
+import { shallow } from "enzyme";
+import { showMenu } from "devtools-contextmenu";
+
+import { copyToTheClipboard } from "../../../utils/clipboard";
+import PrimaryPanes from "..";
+
+jest.mock("devtools-contextmenu", () => ({ showMenu: jest.fn() }));
+jest.mock("../../../utils/clipboard", () => ({
+  copyToTheClipboard: jest.fn()
+}));
+
+describe("PrimaryPanes", () => {
+  afterEach(() => {
+    copyToTheClipboard.mockClear();
+    showMenu.mockClear();
+  });
+
+  describe("with custom root", () => {
+    it("renders custom root source list", async () => {
+      const { component } = render({
+        projectRoot: "mdn.com"
+      });
+      expect(component).toMatchSnapshot();
+    });
+
+    it("calls clearProjectDirectoryRoot on click", async () => {
+      const { component, props } = render({
+        projectRoot: "mdn"
+      });
+      component.find(".sources-clear-root").simulate("click");
+      expect(props.clearProjectDirectoryRoot).toHaveBeenCalled();
+    });
+
+    it("renders empty custom root source list", async () => {
+      const { component } = render({
+        projectRoot: "custom",
+        sources: {}
+      });
+      expect(component).toMatchSnapshot();
+    });
+  });
+});
+
+function generateDefaults(overrides) {
+  return {
+    horizontal: false,
+    projectRoot: "",
+    sourceSearchOn: false,
+    setPrimaryPaneTab: jest.fn(),
+    setActiveSearch: jest.fn(),
+    closeActiveSearch: jest.fn(),
+    clearProjectDirectoryRoot: jest.fn(),
+    threads: [],
+    ...overrides
+  };
+}
+
+function render(overrides = {}) {
+  const props = generateDefaults(overrides);
+  const component = shallow(<PrimaryPanes.WrappedComponent {...props} />);
+  const defaultState = component.state();
+  const instance = component.instance();
+
+  instance.shouldComponentUpdate = () => true;
+
+  return { component, props, defaultState, instance };
+}
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/tests/SourcesTree.spec.js
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/SourcesTree.spec.js
@@ -193,41 +193,16 @@ describe("SourcesTree", () => {
       const { component, props } = render({ focused: item });
       await component
         .find(".sources-list")
         .simulate("keydown", { keyCode: 13 });
       // expect(props.selectSource).toHaveBeenCalledWith(item.contents.id);
     });
   });
 
-  describe("with custom root", () => {
-    it("renders custom root source list", async () => {
-      const { component } = render({
-        projectRoot: "mdn.com"
-      });
-      expect(component).toMatchSnapshot();
-    });
-
-    it("calls clearProjectDirectoryRoot on click", async () => {
-      const { component, props } = render({
-        projectRoot: "mdn"
-      });
-      component.find(".sources-clear-root").simulate("click");
-      expect(props.clearProjectDirectoryRoot).toHaveBeenCalled();
-    });
-
-    it("renders empty custom root source list", async () => {
-      const { component } = render({
-        projectRoot: "custom",
-        sources: {}
-      });
-      expect(component).toMatchSnapshot();
-    });
-  });
-
   describe("selectItem", () => {
     it("should select item with no children", async () => {
       const { instance, props } = render();
       instance.selectItem(createMockItem());
       expect(props.selectSource).toHaveBeenCalledWith(
         "server1.conn13.child1/39"
       );
     });
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/PrimaryPanes.spec.js.snap
@@ -0,0 +1,129 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PrimaryPanes with custom root renders custom root source list 1`] = `
+<Tabs
+  activeIndex={1}
+  className="sources-panel"
+  onActivateTab={[Function]}
+>
+  <TabList
+    activeIndex={0}
+    className="source-outline-tabs"
+    onActivateTab={[Function]}
+    vertical={false}
+  >
+    <Tab
+      active={false}
+      className="tab sources-tab"
+      key="sources-tab"
+    >
+      Sources
+    </Tab>
+    <Tab
+      active={false}
+      className="tab outline-tab"
+      key="outline-tab"
+    >
+      Outline
+    </Tab>
+  </TabList>
+  <TabPanels
+    activeIndex={0}
+    className="source-outline-panel has-root"
+    hasFocusableContent={true}
+  >
+    <div>
+      <div
+        className="sources-clear-root-container"
+        key="root"
+      >
+        <button
+          className="sources-clear-root"
+          onClick={[Function]}
+          title="Remove directory root"
+        >
+          <Svg
+            name="home"
+          />
+          <Svg
+            name="breadcrumb"
+          />
+          <span
+            className="sources-clear-root-label"
+          >
+            mdn.com
+          </span>
+        </button>
+      </div>
+    </div>
+    <Connect(Outline)
+      alphabetizeOutline={false}
+      onAlphabetizeClick={[Function]}
+    />
+  </TabPanels>
+</Tabs>
+`;
+
+exports[`PrimaryPanes with custom root renders empty custom root source list 1`] = `
+<Tabs
+  activeIndex={1}
+  className="sources-panel"
+  onActivateTab={[Function]}
+>
+  <TabList
+    activeIndex={0}
+    className="source-outline-tabs"
+    onActivateTab={[Function]}
+    vertical={false}
+  >
+    <Tab
+      active={false}
+      className="tab sources-tab"
+      key="sources-tab"
+    >
+      Sources
+    </Tab>
+    <Tab
+      active={false}
+      className="tab outline-tab"
+      key="outline-tab"
+    >
+      Outline
+    </Tab>
+  </TabList>
+  <TabPanels
+    activeIndex={0}
+    className="source-outline-panel has-root"
+    hasFocusableContent={true}
+  >
+    <div>
+      <div
+        className="sources-clear-root-container"
+        key="root"
+      >
+        <button
+          className="sources-clear-root"
+          onClick={[Function]}
+          title="Remove directory root"
+        >
+          <Svg
+            name="home"
+          />
+          <Svg
+            name="breadcrumb"
+          />
+          <span
+            className="sources-clear-root-label"
+          >
+            custom
+          </span>
+        </button>
+      </div>
+    </div>
+    <Connect(Outline)
+      alphabetizeOutline={false}
+      onAlphabetizeClick={[Function]}
+    />
+  </TabPanels>
+</Tabs>
+`;
--- a/devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
+++ b/devtools/client/debugger/new/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
@@ -50,80 +50,8 @@ exports[`SourcesTree on receiving new pr
   <div
     className="no-sources-message"
     key="empty"
   >
     This page has no sources
   </div>
 </div>
 `;
-
-exports[`SourcesTree with custom root renders custom root source list 1`] = `
-<div
-  className="sources-pane sources-list-custom-root"
-  key="pane"
->
-  <div
-    className="sources-clear-root-container"
-    key="root"
-  >
-    <button
-      className="sources-clear-root"
-      onClick={[Function]}
-      title="Remove directory root"
-    >
-      <Svg
-        name="home"
-      />
-      <Svg
-        name="breadcrumb"
-      />
-      <span
-        className="sources-clear-root-label"
-      >
-        mdn.com
-      </span>
-    </button>
-  </div>
-  <div
-    className="no-sources-message"
-    key="empty"
-  >
-    This directory root has no sources
-  </div>
-</div>
-`;
-
-exports[`SourcesTree with custom root renders empty custom root source list 1`] = `
-<div
-  className="sources-pane sources-list-custom-root"
-  key="pane"
->
-  <div
-    className="sources-clear-root-container"
-    key="root"
-  >
-    <button
-      className="sources-clear-root"
-      onClick={[Function]}
-      title="Remove directory root"
-    >
-      <Svg
-        name="home"
-      />
-      <Svg
-        name="breadcrumb"
-      />
-      <span
-        className="sources-clear-root-label"
-      >
-        custom
-      </span>
-    </button>
-  </div>
-  <div
-    className="no-sources-message"
-    key="empty"
-  >
-    This directory root has no sources
-  </div>
-</div>
-`;