Bug 1588803-Show sources after setting directory root r=davidwalsh

1.Show sources and thread name after a thread is set to root
2.Show sources under chrome://
3.Show extension name and icon if an extension directory is set to root

{F1665109}
I'm building a better understanding of sources, sourcesTree and their utils. There could be better solutions than the ones I thought of. I'll appreciate feedback or discussions!

Differential Revision: https://phabricator.services.mozilla.com/D50438

--HG--
extra : moz-landing-system : lando
This commit is contained in:
chujun 2019-10-30 07:32:39 +00:00
parent ac5aff0e5b
commit 0815c788fc
10 changed files with 625 additions and 25 deletions

View File

@ -194,13 +194,16 @@ export function clearProjectDirectoryRoot(cx: Context) {
export function setProjectDirectoryRoot(cx: Context, newRoot: string) {
return ({ dispatch, getState }: ThunkArgs) => {
// Remove the thread actor ID from the root path
const threadActor = startsWithThreadActor(getState(), newRoot);
let curRoot = getProjectDirectoryRoot(getState());
// Remove the thread actor ID from the root path
if (threadActor) {
newRoot = newRoot.slice(threadActor.length + 1);
curRoot = curRoot.slice(threadActor.length + 1);
}
const curRoot = getProjectDirectoryRoot(getState());
if (newRoot && curRoot) {
const newRootArr = newRoot.replace(/\/+/g, "/").split("/");
const curRootArr = curRoot

View File

@ -28,6 +28,7 @@ import {
isOriginal as isOriginalSource,
getSourceQueryString,
isUrlExtension,
isExtensionDirectoryPath,
shouldBlackbox,
} from "../../utils/source";
import { isDirectory, getPathWithoutThread } from "../../utils/sources-tree";
@ -241,7 +242,7 @@ class SourceTreeItem extends Component<Props, State> {
return <AccessibleImage className="webpack" />;
} else if (item.name === "ng://") {
return <AccessibleImage className="angular" />;
} else if (isUrlExtension(item.path) && depth === 1) {
} else if (isExtensionDirectoryPath(item.path)) {
return <AccessibleImage className="extension" />;
}
@ -263,7 +264,10 @@ class SourceTreeItem extends Component<Props, State> {
if (isDirectory(item)) {
// Domain level
if (depth === 1 && projectRoot === "") {
if (
(depth === 1 && projectRoot === "") ||
(depth === 0 && threads.find(thrd => thrd.actor === projectRoot))
) {
return <AccessibleImage className="globe-small" />;
}
return <AccessibleImage className="folder" />;
@ -379,7 +383,7 @@ function getSourceContentValue(state, source: Source) {
}
function isExtensionDirectory(depth, extensionName) {
return extensionName && depth === 1;
return extensionName && (depth === 1 || depth === 0);
}
const mapStateToProps = (state, props: OwnProps) => {

View File

@ -10,13 +10,14 @@ import { Tab, Tabs, TabList, TabPanels } from "react-aria-components/src/tabs";
import actions from "../../actions";
import {
getDisplayedSources,
getActiveSearch,
getProjectDirectoryRoot,
getSelectedPrimaryPaneTab,
getAllThreads,
getContext,
getExtensionNameBySourceUrl,
} from "../../selectors";
import { isExtensionDirectoryPath } from "../../utils/source";
import { features, prefs } from "../../utils/prefs";
import { connect } from "../../utils/connect";
import { formatKeyShortcut } from "../../utils/text";
@ -25,7 +26,6 @@ import Outline from "./Outline";
import SourcesTree from "./SourcesTree";
import AccessibleImage from "../shared/AccessibleImage";
import type { SourcesMapByThread } from "../../reducers/types";
import type { SelectedPrimaryPaneTabType } from "../../selectors";
import type { Thread, Context } from "../../types";
@ -41,9 +41,9 @@ type OwnProps = {|
type Props = {
cx: Context,
selectedTab: SelectedPrimaryPaneTabType,
sources: SourcesMapByThread,
horizontal: boolean,
projectRoot: string,
rootExtensionName: ?string,
sourceSearchOn: boolean,
setPrimaryPaneTab: typeof actions.setPrimaryPaneTab,
setActiveSearch: typeof actions.setActiveSearch,
@ -79,6 +79,26 @@ class PrimaryPanes extends Component<Props, State> {
}
};
getRootLabel = (projectRoot: string) => {
const { threads, rootExtensionName } = this.props;
const targetThread = threads.find(thread => thread.actor === projectRoot);
if (targetThread) {
return targetThread.name;
} else if (rootExtensionName) {
return rootExtensionName;
} else if (projectRoot.endsWith("://")) {
if (projectRoot === "ng://") {
return "Angular";
} else if (projectRoot === "webpack://") {
return "Webpack";
}
return `${unescape(projectRoot)}`;
}
return projectRoot.split("/").pop();
};
renderOutlineTabs() {
if (!features.outline) {
return;
@ -112,7 +132,7 @@ class PrimaryPanes extends Component<Props, State> {
return null;
}
const rootLabel = projectRoot.split("/").pop();
const rootLabel = this.getRootLabel(projectRoot);
return (
<div key="root" className="sources-clear-root-container">
@ -166,14 +186,21 @@ class PrimaryPanes extends Component<Props, State> {
}
}
const mapStateToProps = state => ({
cx: getContext(state),
selectedTab: getSelectedPrimaryPaneTab(state),
sources: getDisplayedSources(state),
sourceSearchOn: getActiveSearch(state) === "source",
threads: getAllThreads(state),
projectRoot: getProjectDirectoryRoot(state),
});
const mapStateToProps = state => {
const newProjectRoot = getProjectDirectoryRoot(state);
const extensionAsRoot = isExtensionDirectoryPath(newProjectRoot);
return {
cx: getContext(state),
selectedTab: getSelectedPrimaryPaneTab(state),
sourceSearchOn: getActiveSearch(state) === "source",
threads: getAllThreads(state),
projectRoot: newProjectRoot,
rootExtensionName: extensionAsRoot
? getExtensionNameBySourceUrl(state, newProjectRoot)
: null,
};
};
const connector = connect<Props, OwnProps, _, _, _, _>(
mapStateToProps,

View File

@ -46,6 +46,23 @@ describe("PrimaryPanes", () => {
expect(component).toMatchSnapshot();
});
});
describe("with a thread set to root", () => {
it("uses the thread name as root label", () => {
const { component } = render({
projectRoot: "FakeThread",
threads: [
{
actor: "FakeThread",
name: "Main Thread",
type: "mainThread",
url: "http://a",
},
],
});
expect(component).toMatchSnapshot();
});
});
});
function generateDefaults(overrides) {

View File

@ -282,7 +282,36 @@ describe("SourceTreeItem", () => {
"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856"
);
const node = render({ item, depth: 0 });
const node = render({ item, depth: 1 });
expect(node).toMatchSnapshot();
});
it("should show icon for moz-extension item when a thread is set to root", async () => {
const item = createMockDirectory(
"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856"
);
const node = render({
item,
depth: 0,
projectRoot: "server1.conn13.child1/thread19",
threads: [
{ name: "Main Thread", actor: "server1.conn13.child1/thread19" },
],
});
expect(node).toMatchSnapshot();
});
it("should show icon for domain item when a thread is set to root", async () => {
const item = createMockDirectory();
const node = render({
item,
depth: 0,
projectRoot: "server1.conn13.child1/thread19",
threads: [
{ name: "Main Thread", actor: "server1.conn13.child1/thread19" },
],
});
expect(node).toMatchSnapshot();
});

View File

@ -137,3 +137,81 @@ exports[`PrimaryPanes with custom root renders empty custom root source list 1`]
</TabPanels>
</Tabs>
`;
exports[`PrimaryPanes with a thread set to root uses the thread name as root label 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
className="threads-list"
>
<div
className="sources-clear-root-container"
key="root"
>
<button
className="sources-clear-root"
onClick={[Function]}
title="Remove directory root"
>
<AccessibleImage
className="home"
/>
<AccessibleImage
className="breadcrumb"
/>
<span
className="sources-clear-root-label"
>
Main Thread
</span>
</button>
</div>
<Connect(SourcesTree)
threads={
Array [
Object {
"actor": "FakeThread",
"name": "Main Thread",
"type": "mainThread",
"url": "http://a",
},
]
}
/>
</div>
<Connect(Outline)
alphabetizeOutline={false}
onAlphabetizeClick={[Function]}
/>
</TabPanels>
</Tabs>
`;

View File

@ -1700,7 +1700,7 @@ Object {
className="arrow"
/>
<AccessibleImage
className="folder"
className="extension"
/>
<span
className="label"
@ -1719,7 +1719,7 @@ Object {
"props": Object {
"clearProjectDirectoryRoot": [MockFunction],
"debuggeeUrl": "http://mdn.com",
"depth": 0,
"depth": 1,
"expanded": false,
"focusItem": [MockFunction],
"item": Object {
@ -1761,7 +1761,7 @@ Object {
"_element": <SourceTreeItem
clearProjectDirectoryRoot={[MockFunction]}
debuggeeUrl="http://mdn.com"
depth={0}
depth={1}
expanded={false}
focusItem={[MockFunction]}
item={
@ -1812,7 +1812,7 @@ Object {
className="arrow"
/>
<AccessibleImage
className="folder"
className="extension"
/>
<span
className="label"
@ -1830,7 +1830,7 @@ Object {
"props": Object {
"clearProjectDirectoryRoot": [MockFunction],
"debuggeeUrl": "http://mdn.com",
"depth": 0,
"depth": 1,
"expanded": false,
"focusItem": [MockFunction],
"item": Object {
@ -1865,6 +1865,368 @@ Object {
}
`;
exports[`SourceTreeItem renderItem should show icon for moz-extension item when a thread is set to root 1`] = `
Object {
"component": <div
className="node"
key="moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856"
onClick={[Function]}
onContextMenu={[Function]}
title=""
>
<AccessibleImage
className="arrow"
/>
<AccessibleImage
className="extension"
/>
<span
className="label"
>
moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856
</span>
</div>,
"defaultState": null,
"instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function],
"context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function],
"onContextMenu": [Function],
"props": Object {
"clearProjectDirectoryRoot": [MockFunction],
"debuggeeUrl": "http://mdn.com",
"depth": 0,
"expanded": false,
"focusItem": [MockFunction],
"item": Object {
"contents": Array [],
"name": "moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"path": "moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"type": "directory",
},
"projectRoot": "server1.conn13.child1/thread19",
"selectItem": [MockFunction],
"setExpanded": [MockFunction],
"setProjectDirectoryRoot": [MockFunction],
"source": Object {
"extensionName": null,
"id": "server1.conn13.child1/39",
"introductionType": undefined,
"introductionUrl": null,
"isBlackBoxed": false,
"isExtension": false,
"isPrettyPrinted": false,
"isWasm": false,
"relativeUrl": "http://mdn.com/one.js",
"url": "http://mdn.com/one.js",
},
"threads": Array [
Object {
"actor": "server1.conn13.child1/thread19",
"name": "Main Thread",
},
],
"toggleBlackBox": [MockFunction],
},
"refs": Object {},
"setState": [Function],
"state": null,
"updater": Updater {
"_callbacks": Array [],
"_renderer": ReactShallowRenderer {
"_context": Object {},
"_element": <SourceTreeItem
clearProjectDirectoryRoot={[MockFunction]}
debuggeeUrl="http://mdn.com"
depth={0}
expanded={false}
focusItem={[MockFunction]}
item={
Object {
"contents": Array [],
"name": "moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"path": "moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"type": "directory",
}
}
projectRoot="server1.conn13.child1/thread19"
selectItem={[MockFunction]}
setExpanded={[MockFunction]}
setProjectDirectoryRoot={[MockFunction]}
source={
Object {
"extensionName": null,
"id": "server1.conn13.child1/39",
"introductionType": undefined,
"introductionUrl": null,
"isBlackBoxed": false,
"isExtension": false,
"isPrettyPrinted": false,
"isWasm": false,
"relativeUrl": "http://mdn.com/one.js",
"url": "http://mdn.com/one.js",
}
}
threads={
Array [
Object {
"actor": "server1.conn13.child1/thread19",
"name": "Main Thread",
},
]
}
toggleBlackBox={[MockFunction]}
/>,
"_forcedUpdate": false,
"_instance": [Circular],
"_newState": null,
"_rendered": <div
className="node"
onClick={[Function]}
onContextMenu={[Function]}
title=""
>
<AccessibleImage
className="arrow"
/>
<AccessibleImage
className="extension"
/>
<span
className="label"
>
moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856
</span>
</div>,
"_rendering": false,
"_updater": [Circular],
},
},
Symbol(enzyme.__setState__): [Function],
},
"props": Object {
"clearProjectDirectoryRoot": [MockFunction],
"debuggeeUrl": "http://mdn.com",
"depth": 0,
"expanded": false,
"focusItem": [MockFunction],
"item": Object {
"contents": Array [],
"name": "moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"path": "moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856",
"type": "directory",
},
"projectRoot": "server1.conn13.child1/thread19",
"selectItem": [MockFunction],
"setExpanded": [MockFunction],
"setProjectDirectoryRoot": [MockFunction],
"source": Object {
"extensionName": null,
"id": "server1.conn13.child1/39",
"introductionType": undefined,
"introductionUrl": null,
"isBlackBoxed": false,
"isExtension": false,
"isPrettyPrinted": false,
"isWasm": false,
"relativeUrl": "http://mdn.com/one.js",
"url": "http://mdn.com/one.js",
},
"threads": Array [
Object {
"actor": "server1.conn13.child1/thread19",
"name": "Main Thread",
},
],
"toggleBlackBox": [MockFunction],
},
}
`;
exports[`SourceTreeItem renderItem should show icon for domain item when a thread is set to root 1`] = `
Object {
"component": <div
className="node"
key="domain/subfolder"
onClick={[Function]}
onContextMenu={[Function]}
title=""
>
<AccessibleImage
className="arrow"
/>
<AccessibleImage
className="globe-small"
/>
<span
className="label"
>
folder
</span>
</div>,
"defaultState": null,
"instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function],
"context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function],
"onContextMenu": [Function],
"props": Object {
"clearProjectDirectoryRoot": [MockFunction],
"debuggeeUrl": "http://mdn.com",
"depth": 0,
"expanded": false,
"focusItem": [MockFunction],
"item": Object {
"contents": Array [],
"name": "folder",
"path": "domain/subfolder",
"type": "directory",
},
"projectRoot": "server1.conn13.child1/thread19",
"selectItem": [MockFunction],
"setExpanded": [MockFunction],
"setProjectDirectoryRoot": [MockFunction],
"source": Object {
"extensionName": null,
"id": "server1.conn13.child1/39",
"introductionType": undefined,
"introductionUrl": null,
"isBlackBoxed": false,
"isExtension": false,
"isPrettyPrinted": false,
"isWasm": false,
"relativeUrl": "http://mdn.com/one.js",
"url": "http://mdn.com/one.js",
},
"threads": Array [
Object {
"actor": "server1.conn13.child1/thread19",
"name": "Main Thread",
},
],
"toggleBlackBox": [MockFunction],
},
"refs": Object {},
"setState": [Function],
"state": null,
"updater": Updater {
"_callbacks": Array [],
"_renderer": ReactShallowRenderer {
"_context": Object {},
"_element": <SourceTreeItem
clearProjectDirectoryRoot={[MockFunction]}
debuggeeUrl="http://mdn.com"
depth={0}
expanded={false}
focusItem={[MockFunction]}
item={
Object {
"contents": Array [],
"name": "folder",
"path": "domain/subfolder",
"type": "directory",
}
}
projectRoot="server1.conn13.child1/thread19"
selectItem={[MockFunction]}
setExpanded={[MockFunction]}
setProjectDirectoryRoot={[MockFunction]}
source={
Object {
"extensionName": null,
"id": "server1.conn13.child1/39",
"introductionType": undefined,
"introductionUrl": null,
"isBlackBoxed": false,
"isExtension": false,
"isPrettyPrinted": false,
"isWasm": false,
"relativeUrl": "http://mdn.com/one.js",
"url": "http://mdn.com/one.js",
}
}
threads={
Array [
Object {
"actor": "server1.conn13.child1/thread19",
"name": "Main Thread",
},
]
}
toggleBlackBox={[MockFunction]}
/>,
"_forcedUpdate": false,
"_instance": [Circular],
"_newState": null,
"_rendered": <div
className="node"
onClick={[Function]}
onContextMenu={[Function]}
title=""
>
<AccessibleImage
className="arrow"
/>
<AccessibleImage
className="globe-small"
/>
<span
className="label"
>
folder
</span>
</div>,
"_rendering": false,
"_updater": [Circular],
},
},
Symbol(enzyme.__setState__): [Function],
},
"props": Object {
"clearProjectDirectoryRoot": [MockFunction],
"debuggeeUrl": "http://mdn.com",
"depth": 0,
"expanded": false,
"focusItem": [MockFunction],
"item": Object {
"contents": Array [],
"name": "folder",
"path": "domain/subfolder",
"type": "directory",
},
"projectRoot": "server1.conn13.child1/thread19",
"selectItem": [MockFunction],
"setExpanded": [MockFunction],
"setProjectDirectoryRoot": [MockFunction],
"source": Object {
"extensionName": null,
"id": "server1.conn13.child1/39",
"introductionType": undefined,
"introductionUrl": null,
"isBlackBoxed": false,
"isExtension": false,
"isPrettyPrinted": false,
"isWasm": false,
"relativeUrl": "http://mdn.com/one.js",
"url": "http://mdn.com/one.js",
},
"threads": Array [
Object {
"actor": "server1.conn13.child1/thread19",
"name": "Main Thread",
},
],
"toggleBlackBox": [MockFunction],
},
}
`;
exports[`SourceTreeItem renderItem should show icon for webpack item 1`] = `
Object {
"component": <div

View File

@ -60,6 +60,7 @@ import {
type SourceActorId,
type SourceActorOuterState,
} from "./source-actors";
import { getThreads, getMainThread } from "./threads";
import type {
Source,
SourceId,
@ -796,6 +797,7 @@ const queryAllDisplayedSources: ReduceQuery<
projectDirectoryRoot: string,
chromeAndExtensionsEnabled: boolean,
debuggeeIsWebExtension: boolean,
threadActors: Array<ThreadId>,
|},
Array<SourceId>
> = makeReduceQuery(
@ -807,11 +809,12 @@ const queryAllDisplayedSources: ReduceQuery<
projectDirectoryRoot,
chromeAndExtensionsEnabled,
debuggeeIsWebExtension,
threadActors,
}
) => ({
id: resource.id,
displayed:
underRoot(resource, projectDirectoryRoot) &&
underRoot(resource, projectDirectoryRoot, threadActors) &&
(!resource.isExtension ||
chromeAndExtensionsEnabled ||
debuggeeIsWebExtension),
@ -833,6 +836,10 @@ function getAllDisplayedSources(
projectDirectoryRoot: state.sources.projectDirectoryRoot,
chromeAndExtensionsEnabled: state.sources.chromeAndExtenstionsEnabled,
debuggeeIsWebExtension: state.threads.isWebExtension,
threadActors: [
getMainThread(state).actor,
...getThreads(state).map(t => t.actor),
],
});
}

View File

@ -28,6 +28,7 @@ import type {
SourceActor,
SourceContent,
SourceLocation,
ThreadId,
} from "../types";
import { isFulfilled, type AsyncValue } from "./async-value";
import type { Symbols } from "../reducers/types";
@ -483,7 +484,23 @@ export function getRelativeUrl(source: Source, root: string) {
return url.slice(url.indexOf(root) + root.length + 1);
}
export function underRoot(source: Source, root: string) {
export function underRoot(
source: Source,
root: string,
threadActors: Array<ThreadId>
) {
// source.url doesn't include thread actor ID, so remove the thread actor ID from the root
threadActors.forEach(threadActor => {
if (root.includes(threadActor)) {
root = root.slice(threadActor.length + 1);
}
});
if (source.url && source.url.includes("chrome://")) {
const { group, path } = getURL(source);
return (group + path).includes(root);
}
return source.url && source.url.includes(root);
}
@ -509,6 +526,17 @@ export function isUrlExtension(url: string) {
return url.includes("moz-extension:") || url.includes("chrome-extension");
}
export function isExtensionDirectoryPath(url: string) {
if (isUrlExtension(url)) {
const urlArr = url.replace(/\/+/g, "/").split("/");
let extensionIndex = urlArr.indexOf("moz-extension:");
if (extensionIndex === -1) {
extensionIndex = urlArr.indexOf("chrome-extension:");
}
return !urlArr[extensionIndex + 2];
}
}
export function getPlainUrl(url: string): string {
const queryStart = url.indexOf("?");
return queryStart !== -1 ? url.slice(0, queryStart) : url;

View File

@ -13,7 +13,9 @@ import {
getSourceLineCount,
isThirdParty,
isJavaScript,
underRoot,
isUrlExtension,
isExtensionDirectoryPath,
} from "../source.js";
import {
@ -504,6 +506,35 @@ describe("sources", () => {
});
});
describe("underRoot", () => {
const threadActors = ["server0.conn1.child1/thread19"];
it("should detect normal source urls", () => {
const source = makeMockSource(
"resource://activity-stream/vendor/react.js"
);
expect(
underRoot(source, "resource://activity-stream", threadActors)
).toBe(true);
});
it("should detect source urls under chrome:// as root", () => {
const source = makeMockSource(
"chrome://browser/content/contentSearchUI.js"
);
expect(underRoot(source, "chrome://", threadActors)).toBe(true);
});
it("should detect source urls if root is a thread actor Id", () => {
const source = makeMockSource(
"resource://activity-stream/vendor/react-dom.js"
);
expect(
underRoot(source, "server0.conn1.child1/thread19", threadActors)
).toBe(true);
});
});
describe("isUrlExtension", () => {
it("should detect mozilla extenstion", () => {
expect(isUrlExtension("moz-extension://id/js/content.js")).toBe(true);
@ -515,4 +546,18 @@ describe("sources", () => {
expect(isUrlExtension("https://example.org/init.js")).toBe(false);
});
});
describe("isExtensionDirectoryPath", () => {
it("should detect mozilla extenstion directory", () => {
expect(isExtensionDirectoryPath("moz-extension://id")).toBe(true);
});
it("should detect chrome extenstion directory", () => {
expect(isExtensionDirectoryPath("chrome-extension://id")).toBe(true);
});
it("should return false for child file within the extenstion directory", () => {
expect(isExtensionDirectoryPath("moz-extension://id/js/content.js")).toBe(
false
);
});
});
});