Bug 1471795 - Part 7: Implement tab list. r?jdescottes, r?ladybenko
MozReview-Commit-ID: LeLI9sKvrOI
--- a/devtools/client/aboutdebugging-new/aboutdebugging.css
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.css
@@ -2,16 +2,18 @@
* 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 "resource://devtools/client/themes/variables.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/App.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/RuntimesPane.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/runtime/ThisFirefoxItem.css";
html, body, #mount {
font-size: 15px;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -22,44 +22,44 @@ const Provider =
const actions = require("./src/actions/index");
const { configureStore } = require("./src/create-store");
const ThisFirefox = require("./src/runtimes/this-firefox");
const App = createFactory(require("./src/components/App"));
const AboutDebugging = {
- init() {
+ async init() {
if (!Services.prefs.getBoolPref("devtools.enabled", true)) {
// If DevTools are disabled, navigate to about:devtools.
window.location = "about:devtools?reason=AboutDebugging";
return;
}
this.updateSelectedRuntime = this.updateSelectedRuntime.bind(this);
this.store = configureStore();
this.actions = bindActionCreators(actions, this.store.dispatch);
const thisFirefox = new ThisFirefox();
- this.updateSelectedRuntime(thisFirefox);
+ await this.updateSelectedRuntime(thisFirefox);
render(Provider({ store: this.store }, App({ thisFirefox })), this.mount);
},
destroy() {
unmountComponentAtNode(this.mount);
},
get mount() {
return document.getElementById("mount");
},
- updateSelectedRuntime(runtime) {
- this.actions.updateSelectedRuntime(runtime);
+ async updateSelectedRuntime(runtime) {
+ await this.actions.updateSelectedRuntime(runtime);
},
};
window.addEventListener("DOMContentLoaded", () => {
AboutDebugging.init();
}, { once: true });
window.addEventListener("unload", () => {
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -4,17 +4,20 @@
"use strict";
const {
UPDATE_SELECTED_RUNTIME,
} = require("../constants");
function updateSelectedRuntime(runtime) {
- return {
- type: UPDATE_SELECTED_RUNTIME,
- runtime,
+ return async dispatch => {
+ await runtime.connect();
+ dispatch({
+ type: UPDATE_SELECTED_RUNTIME,
+ runtime,
+ });
};
}
module.exports = {
updateSelectedRuntime,
};
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
+++ b/devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
@@ -5,53 +5,72 @@
"use strict";
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const Runtime = require("../runtimes/runtime");
+const DebugTargetList = createFactory(require("./debugtarget/DebugTargetList"));
const RuntimeInfo = createFactory(require("./RuntimeInfo"));
+const TabItem = createFactory(require("./debugtarget/TabItem"));
class DebugTargetsPane extends PureComponent {
static get propTypes() {
return {
runtime: PropTypes.instanceOf(Runtime).isRequired,
};
}
constructor(props) {
super(props);
this.state = {
info: {},
+ tabs: [],
};
this.update(props.runtime);
}
componentDidUpdate(prevProps) {
if (prevProps.runtime !== this.props.runtime) {
this.update(this.props.runtime);
}
}
update(runtime) {
this.updateRuntimeInfo(runtime);
+ this.updateTabs(runtime);
}
async updateRuntimeInfo(runtime) {
const info = await runtime.getRuntimeInfo(runtime);
this.setState({ info });
}
+ async updateTabs(runtime) {
+ // Filter out closed tabs (represented as `null`).
+ const tabs = (await runtime.getTabs()).filter(t => !!t);
+ this.setState({ tabs });
+ }
+
render() {
- const { info } = this.state;
+ const {
+ info,
+ tabs,
+ } = this.state;
return dom.div(
{
className: "debug-targets-pane",
},
- RuntimeInfo({ info })
+ RuntimeInfo({ info }),
+ DebugTargetList({
+ className: "debug-target-list--tabs",
+ debugTargetItemComponent: TabItem,
+ debugTargets: tabs,
+ title: "Tabs",
+ })
);
}
}
module.exports = DebugTargetsPane;
--- a/devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css
@@ -2,15 +2,16 @@
* 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/. */
.runtime-info {
align-items: center;
display: flex;
font-size: 30px;
margin-block-start: 60px;
+ margin-block-end: 50px;
}
.runtime-info__icon {
height: 64px;
margin-inline-end: 12px;
width: 64px;
}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css
@@ -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/. */
+
+.debug-target-item {
+ display: flex;
+ margin-block-end: 20px;
+}
+
+.debug-target-item__icon {
+ height: 36px;
+ margin-inline-end: 18px;
+ width: 36px;
+}
+
+.debug-target-item__info__name {
+ font-size: 20px;
+ margin-block-end: 2px;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+/**
+ * This component shows the debug target as item in DebugTargetsPane.
+ */
+class DebugTargetItem extends PureComponent {
+ static get propTypes() {
+ return {
+ debugTarget: PropTypes.object.isRequired,
+ };
+ }
+
+ /**
+ * Return icon src.
+ * Subclass should override this method.
+ * @return {String} URL
+ */
+ getIcon() {
+ throw new Error("Subclass of DebugTargetItem should override getIcon()");
+ }
+
+ /**
+ * Return name of this debug target.
+ * Subclass should override this method.
+ * @return {String} name
+ */
+ getName() {
+ throw new Error("Subclass of DebugTargetItem should override getName()");
+ }
+
+ /**
+ * Render components which show more information.
+ * Subclass should override this method.
+ * @return {Array} or {ReactComponent} or null
+ */
+ renderDetailComponents() {
+ throw new Error("Subclass of DebugTargetItem should override " +
+ "renderDetailComponents()");
+ }
+
+ renderIcon() {
+ return dom.img(
+ {
+ className: "debug-target-item__icon",
+ src: this.getIcon(),
+ }
+ );
+ }
+
+ renderInfo() {
+ const name = this.getName();
+
+ return dom.div(
+ {
+ className: "debug-target-item__info",
+ },
+ dom.div(
+ {
+ className: "debug-target-item__info__name",
+ title: name,
+ },
+ name
+ ),
+ dom.div(
+ {
+ className: "debug-target-item__info__detail",
+ },
+ this.renderDetailComponents()
+ ),
+ );
+ }
+
+ render() {
+ return dom.li(
+ {
+ className: "debug-target-item",
+ },
+ this.renderIcon(),
+ this.renderInfo(),
+ );
+ }
+}
+
+module.exports = DebugTargetItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.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/. */
+
+.debug-target-list {
+ margin-block-end: 30px;
+}
+
+.debug-target-list__title {
+ margin-block-start: 10px;
+ margin-block-end: 10px;
+}
+
+.debug-target-list__ul {
+ margin-inline-start: 60px;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.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/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+class DebugTargetList extends PureComponent {
+ static get propTypes() {
+ return {
+ className: PropTypes.string.isRequired,
+ debugTargetItemComponent: PropTypes.any.isRequired,
+ debugTargets: PropTypes.arrayOf(PropTypes.object).isRequired,
+ title: PropTypes.string.isRequired,
+ };
+ }
+
+ render() {
+ const { className, debugTargetItemComponent, debugTargets, title } = this.props;
+
+ return dom.div(
+ {
+ className: `debug-target-list ${ className }`,
+ },
+ dom.h2(
+ {
+ className: `debug-target-list__title ${ className }__title`,
+ },
+ title
+ ),
+ dom.ul(
+ {
+ className: `debug-target-list__ul ${ className }__ul`,
+ },
+ debugTargets.map(debugTarget => debugTargetItemComponent({ debugTarget }))
+ )
+ );
+ }
+}
+
+module.exports = DebugTargetList;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/TabItem.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const DebugTargetItem = require("./DebugTargetItem");
+
+class TabItem extends DebugTargetItem {
+ getIcon() {
+ const { debugTarget } = this.props;
+
+ if (debugTarget.favicon) {
+ const base64Favicon = btoa(String.fromCharCode.apply(String, debugTarget.favicon));
+ return `data:image/png;base64,${ base64Favicon }`;
+ }
+
+ return "chrome://devtools/skin/images/globe.svg";
+ }
+
+ getName() {
+ const { debugTarget } = this.props;
+ // If the title is empty, display the url instead.
+ return debugTarget.title || debugTarget.url;
+ }
+
+ renderDetailComponents() {
+ const { debugTarget } = this.props;
+
+ return dom.div(
+ {
+ className: "debug-target-item__info__detail__tab-url",
+ },
+ debugTarget.url
+ );
+ }
+}
+
+module.exports = TabItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
@@ -0,0 +1,11 @@
+# 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/.
+
+DevToolsModules(
+ 'DebugTargetItem.css',
+ 'DebugTargetItem.js',
+ 'DebugTargetList.css',
+ 'DebugTargetList.js',
+ 'TabItem.js'
+)
--- a/devtools/client/aboutdebugging-new/src/components/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/moz.build
@@ -1,13 +1,14 @@
# 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 += [
+ 'debugtarget',
'runtime',
]
DevToolsModules(
'App.css',
'App.js',
'DebugTargetsPane.css',
'DebugTargetsPane.js',
--- a/devtools/client/aboutdebugging-new/src/create-store.js
+++ b/devtools/client/aboutdebugging-new/src/create-store.js
@@ -1,18 +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/. */
"use strict";
-const { createStore } = require("devtools/client/shared/vendor/redux");
+const { applyMiddleware, createStore } = require("devtools/client/shared/vendor/redux");
+const { thunk } = require("devtools/client/shared/redux/middleware/thunk");
const rootReducer = require("./reducers/index");
const { RuntimesState } = require("./reducers/runtimes-state");
exports.configureStore = function() {
const initialState = {
runtimes: new RuntimesState(),
};
- return createStore(rootReducer, initialState);
+ return createStore(rootReducer, initialState, applyMiddleware(thunk));
};
--- a/devtools/client/aboutdebugging-new/src/runtimes/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/runtimes/runtime.js
@@ -4,26 +4,43 @@
"use strict";
/**
* This class represents a runtime, such as a remote Firefox.
*/
class Runtime {
/**
+ * Connect to this runtime.
+ * Subclass should override this method.
+ */
+ async connect() {
+ throw new Error("Subclass of Runtime should override connect()");
+ }
+
+ /**
* Return this runtime's information.
* Subclass should override this method.
* @return {Object}
* {
* icon: URL of the icon which represents this runtime
* e.g. chrome://branding/content/icon64.png
* name: Name of this runtime
* e.g. Firefox
* version: Version of this runtime
* e.g. 62.0a1
* }
*/
async getRuntimeInfo() {
throw new Error("Subclass of Runtime should override getRuntimeInfo()");
}
+
+ /**
+ * Return tabs on this runtime.
+ * Subclass should override this method.
+ * @return {Array}
+ */
+ async getTabs() {
+ throw new Error("Subclass of Runtime should override getTabs()");
+ }
}
module.exports = Runtime;
--- a/devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
+++ b/devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
@@ -1,25 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
+const { DebuggerClient } = require("devtools/shared/client/debugger-client");
+const { DebuggerServer } = require("devtools/server/main");
const Services = require("Services");
const Runtime = require("./runtime");
/**
* This class represents the Firefox instance which runs in the same environment that
* opened about:debugging.
*/
class ThisFirefox extends Runtime {
+ async connect() {
+ // Setup a server if we don't have one already running
+ DebuggerServer.init();
+ DebuggerServer.registerAllActors();
+ const transport = DebuggerServer.connectPipe();
+ this.client = new DebuggerClient(transport);
+ return this.client.connect();
+ }
+
async getRuntimeInfo() {
return {
icon: "chrome://branding/content/icon64.png",
name: Services.appinfo.name,
version: Services.appinfo.version,
};
}
+
+ async getTabs() {
+ const { tabs } = await this.client.listTabs({ favicons: true });
+ return tabs;
+ }
}
module.exports = ThisFirefox;