Bug 1471795: wip. r? draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 05 Jul 2018 17:50:47 +0900
changeset 814414 2144c999f44f1cfb98c6544740b97c4f51d7eb19
parent 814369 90be04d99fc7941cb9b7186bf5f95e184a4e989a
push id115193
push userbmo:dakatsuka@mozilla.com
push dateThu, 05 Jul 2018 08:57:02 +0000
bugs1471795
milestone63.0a1
Bug 1471795: wip. r? MozReview-Commit-ID: Hu8nrJSxJ53
devtools/client/aboutdebugging-ng/aboutdebugging.css
devtools/client/aboutdebugging-ng/aboutdebugging.js
devtools/client/aboutdebugging-ng/actions.js
devtools/client/aboutdebugging-ng/components/App.js
devtools/client/aboutdebugging-ng/components/AppInfo.js
devtools/client/aboutdebugging-ng/components/DebugPanel.js
devtools/client/aboutdebugging-ng/components/DebugTargetItem.js
devtools/client/aboutdebugging-ng/components/DebugTargetList.js
devtools/client/aboutdebugging-ng/components/ExtensionItem.js
devtools/client/aboutdebugging-ng/components/RuntimeItem.js
devtools/client/aboutdebugging-ng/components/RuntimeList.js
devtools/client/aboutdebugging-ng/components/TabItem.js
devtools/client/aboutdebugging-ng/components/ThisFirefoxItem.js
devtools/client/aboutdebugging-ng/components/WorkerItem.js
devtools/client/aboutdebugging-ng/components/moz.build
devtools/client/aboutdebugging-ng/index.html
devtools/client/aboutdebugging-ng/moz.build
devtools/client/aboutdebugging-ng/reducer.js
devtools/client/aboutdebugging-ng/runtimes/Runtime.js
devtools/client/aboutdebugging-ng/runtimes/ThisFirefox.js
devtools/client/aboutdebugging-ng/runtimes/moz.build
devtools/client/aboutdebugging-ng/store.js
devtools/client/jar.mn
devtools/client/moz.build
devtools/client/preferences/devtools-client.js
devtools/startup/aboutdebugging-registration.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/aboutdebugging.css
@@ -0,0 +1,63 @@
+/* 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/. */
+
+html, body {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+}
+
+ul {
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+
+.app {
+  display: flex;
+}
+
+.runtime-list {
+  padding: 40px;
+}
+
+.runtime-item.selected {
+  color: #0a84ff;
+}
+
+.debug-panel {
+  flex: 1;
+}
+
+.appinfo {
+  align-items: center;
+  display: flex;
+  padding-top: 20px;
+  padding-bottom: 20px;
+}
+
+.appinfo .icon {
+  padding-right: 10px;
+}
+
+.debug-target-list {
+  padding-bottom: 15px;
+}
+
+.debug-target-list ul {
+  padding-left: 20px;
+}
+
+.debug-target-item {
+  align-items: center;
+  display: flex;
+  line-height: 30px;
+}
+
+.debug-target-item .icon {
+  height: 20px;
+  width: 20px;
+  padding-right: 5px;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/aboutdebugging.js
@@ -0,0 +1,66 @@
+/* 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 { BrowserLoader } = ChromeUtils.import(
+  "resource://devtools/client/shared/browser-loader.js", {});
+const { require } = BrowserLoader({
+  baseURI: "resource://devtools/client/aboutdebugging-ng/",
+  window,
+});
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+
+const { createElement, createFactory } =
+  require("devtools/client/shared/vendor/react");
+const { render, unmountComponentAtNode } =
+  require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+
+const App = createFactory(require("./components/App"));
+const Store = require("./store");
+const ThisFirefox = require("./runtimes/ThisFirefox");
+
+const { updateSelectedRuntime } = require("./actions");
+
+const AboutDebugging = {
+  async init() {
+    if (!Services.prefs.getBoolPref("devtools.enabled", true)) {
+      // If DevTools are disabled, navigate to about:devtools.
+      window.location = "about:devtools?reason=AboutDebugging";
+      return;
+    }
+
+    const thisFirefox = new ThisFirefox();
+    const app = App({ thisFirefox });
+    const store = Store();
+    this.store = store;
+    const provider = createElement(Provider, { store }, app);
+    render(provider, document.getElementById("root"));
+
+    await this.updateSelectedRuntime(thisFirefox);
+  },
+
+  destroy() {
+    unmountComponentAtNode(document.getElementById("root"));
+    this.store = null;
+  },
+
+  get state() {
+    return this.store.getState();
+  },
+
+  async updateSelectedRuntime(runtime) {
+    await runtime.connect();
+    this.store.dispatch(updateSelectedRuntime(runtime));
+  },
+};
+
+window.addEventListener("DOMContentLoaded", () => {
+  AboutDebugging.init();
+}, { once: true });
+
+window.addEventListener("unload", () => {
+  AboutDebugging.destroy();
+}, { once: true });
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/actions.js
@@ -0,0 +1,26 @@
+/* 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 { createEnum } = require("devtools/client/shared/enum");
+
+const index = createEnum([
+  // Update selected runtime.
+  "UPDATE_SELECTED_RUNTIME",
+]);
+
+module.exports = {
+  UPDATE_SELECTED_RUNTIME: index.UPDATE_SELECTED_RUNTIME,
+
+  /**
+   * Update selected runtime.
+   */
+  updateSelectedRuntime(selectedRuntime) {
+    return {
+      type: index.UPDATE_SELECTED_RUNTIME,
+      selectedRuntime,
+    };
+  },
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/App.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 { connect } = require("devtools/client/shared/vendor/react-redux");
+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 RuntimeList = createFactory(require("./RuntimeList"));
+const DebugPanel = createFactory(require("./DebugPanel"));
+const ThisFirefox = require("../runtimes/ThisFirefox");
+
+class App extends PureComponent {
+  static get propTypes() {
+    return {
+      selectedRuntime: PropTypes.instanceOf(Runtime),
+      thisFirefox: PropTypes.instanceOf(ThisFirefox).isRequired,
+    };
+  }
+
+  render() {
+    const { selectedRuntime, thisFirefox } = this.props;
+
+    return dom.div(
+      {
+        className: "app",
+      },
+      RuntimeList({ selectedRuntime, thisFirefox }),
+      selectedRuntime ? DebugPanel({ runtime: selectedRuntime }) : null
+    );
+  }
+}
+
+const mapStateToProps = state => {
+  return {
+    selectedRuntime: state.selectedRuntime,
+  };
+};
+
+module.exports = connect(mapStateToProps)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/AppInfo.js
@@ -0,0 +1,36 @@
+/* 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 AppInfo extends PureComponent {
+  static get propTypes() {
+    return {
+      appinfo: PropTypes.object.isRequired,
+    };
+  }
+
+  render() {
+    const { appinfo } = this.props;
+
+    return dom.div(
+      {
+        className: "appinfo",
+      },
+      dom.img({ className: "icon", src: appinfo.icon }),
+      dom.div(
+        {
+          className: "version"
+        },
+        `${ appinfo.name } (${ appinfo.version })`
+      )
+    );
+  }
+}
+
+module.exports = AppInfo;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/DebugPanel.js
@@ -0,0 +1,121 @@
+/* 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 { 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 AppInfo = createFactory(require("./AppInfo"));
+const DebugTargetList = createFactory(require("./DebugTargetList"));
+const ExtensionItem = createFactory(require("./ExtensionItem"));
+const Runtime = require("../runtimes/Runtime");
+const TabItem = createFactory(require("./TabItem"));
+const WorkerItem = createFactory(require("./WorkerItem"));
+
+class DebugPanel extends PureComponent {
+  static get propTypes() {
+    return {
+      runtime: PropTypes.instanceOf(Runtime).isRequired,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      appinfo: null,
+      tabs: [],
+      installedExtensions: [],
+      temporaryExtensions: [],
+      serviceWorkers: [],
+      sharedWorkers: [],
+      otherWorkers: [],
+    };
+    this.update(props.runtime);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    this.update(nextProps.runtime);
+  }
+
+  async update(runtime) {
+    const appinfo = await runtime.getAppInfo();
+    // Filter out closed tabs (represented as `null`).
+    const tabs = (await runtime.getTabs()).filter(t => !!t);
+    const extensions = (await runtime.getExtensions()).filter(t => t.debuggable);
+    const installedExtensions = extensions.filter(t => !t.temporarilyInstalled);
+    const temporaryExtensions = extensions.filter(t => t.temporarilyInstalled);
+    const {
+      service: serviceWorkers,
+      shared: sharedWorkers,
+      other: otherWorkers,
+    } = await runtime.getWorkers();
+
+    this.setState({
+      appinfo,
+      tabs,
+      installedExtensions,
+      temporaryExtensions,
+      serviceWorkers,
+      sharedWorkers,
+      otherWorkers,
+    });
+  }
+
+  render() {
+    const {
+      appinfo,
+      tabs,
+      installedExtensions,
+      temporaryExtensions,
+      serviceWorkers,
+      sharedWorkers,
+      otherWorkers,
+    } = this.state;
+
+    if (!appinfo) {
+      return null;
+    }
+
+    return dom.div(
+      {
+        className: "debug-panel",
+      },
+      AppInfo({ appinfo }),
+      DebugTargetList({
+        debugTargetItemComponent: TabItem,
+        debugTargets: tabs,
+        title: "Tabs",
+      }),
+      DebugTargetList({
+        debugTargetItemComponent: ExtensionItem,
+        debugTargets: temporaryExtensions,
+        title: "Temporary Extensions",
+      }),
+      DebugTargetList({
+        debugTargetItemComponent: ExtensionItem,
+        debugTargets: installedExtensions,
+        title: "Extensions",
+      }),
+      DebugTargetList({
+        debugTargetItemComponent: WorkerItem,
+        debugTargets: serviceWorkers,
+        title: "Service Workers",
+      }),
+      DebugTargetList({
+        debugTargetItemComponent: WorkerItem,
+        debugTargets: sharedWorkers,
+        title: "Shared Workers",
+      }),
+      DebugTargetList({
+        debugTargetItemComponent: WorkerItem,
+        debugTargets: otherWorkers,
+        title: "Other Workers",
+      })
+    );
+  }
+}
+
+module.exports = DebugPanel;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/DebugTargetItem.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/. */
+
+"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 DebugTargetItem extends PureComponent {
+  static get propTypes() {
+    return {
+      debugTarget: PropTypes.object.isRequired,
+    };
+  }
+
+  render() {
+    const { debugTarget } = this.props;
+
+    return dom.li(
+      {
+        className: "debug-target-item",
+      },
+      this.renderComponents(debugTarget)
+    );
+  }
+}
+
+module.exports = DebugTargetItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/DebugTargetList.js
@@ -0,0 +1,36 @@
+/* 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 {
+      debugTargetItemComponent: PropTypes.any.isRequired,
+      debugTargets: PropTypes.arrayOf(PropTypes.object).isRequired,
+      title: PropTypes.string.isRequired,
+    };
+  }
+
+  render() {
+    const { debugTargetItemComponent, debugTargets, title } = this.props;
+
+    return dom.div(
+      {
+        className: "debug-target-list",
+      },
+      dom.div({}, title),
+      dom.ul(
+        {},
+        debugTargets.map(debugTarget => debugTargetItemComponent({ debugTarget }))
+      )
+    );
+  }
+}
+
+module.exports = DebugTargetList;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/ExtensionItem.js
@@ -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/. */
+
+"use strict";
+
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const DebugTargetItem = require("./DebugTargetItem");
+
+class ExtensionItem extends DebugTargetItem {
+  renderComponents(debugTarget) {
+    return [
+      dom.img(
+        {
+          className: "icon",
+          src: debugTarget.iconURL ||
+               "chrome://mozapps/skin/extensions/extensionGeneric.svg",
+        }
+      ),
+      dom.div(
+        {
+          className: "name",
+          title: debugTarget.name,
+        },
+        debugTarget.name
+      ),
+    ];
+  }
+}
+
+module.exports = ExtensionItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/RuntimeItem.js
@@ -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/. */
+
+"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");
+
+const Runtime = require("../runtimes/Runtime");
+
+class RuntimeItem extends PureComponent {
+  static get propTypes() {
+    return {
+      className: PropTypes.string.isRequired,
+      runtime: PropTypes.instanceOf(Runtime).isRequired,
+      selectedRuntime: PropTypes.instanceOf(Runtime),
+    };
+  }
+
+  render() {
+    const { className, runtime, selectedRuntime } = this.props;
+    const isSelected = runtime === selectedRuntime;
+
+    return dom.li(
+      {
+        className: `runtime-item ${ className } ${ isSelected ? "selected" : ""}`,
+      },
+      this.renderComponents(runtime)
+    );
+  }
+}
+
+module.exports = RuntimeItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/RuntimeList.js
@@ -0,0 +1,40 @@
+/* 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 { 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 ThisFirefox = require("../runtimes/ThisFirefox");
+const ThisFirefoxItem = createFactory(require("./ThisFirefoxItem"));
+
+class RuntimeList extends PureComponent {
+  static get propTypes() {
+    return {
+      selectedRuntime: PropTypes.instanceOf(Runtime),
+      thisFirefox: PropTypes.instanceOf(ThisFirefox).isRequired,
+    };
+  }
+
+  render() {
+    const { thisFirefox, selectedRuntime } = this.props;
+
+    return dom.ul(
+      {
+        className: "runtime-list",
+      },
+      ThisFirefoxItem(
+        {
+          runtime: thisFirefox,
+          selectedRuntime,
+        }
+      )
+    );
+  }
+}
+
+module.exports = RuntimeList;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/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 {
+  renderComponents(debugTarget) {
+    let icon = null;
+
+    if (debugTarget.favicon) {
+      const base64Favicon = btoa(String.fromCharCode.apply(String, debugTarget.favicon));
+      icon = "data:image/png;base64," + base64Favicon;
+    } else {
+      icon = "chrome://devtools/skin/images/globe.svg";
+    }
+
+    return [
+      dom.img(
+        {
+          className: "icon",
+          src: icon,
+        }
+      ),
+      dom.div(
+        {
+          className: "name",
+          title: debugTarget.url,
+        },
+        // If the title is empty, display the url instead.
+        debugTarget.title || debugTarget.url
+      ),
+    ];
+  }
+}
+
+module.exports = TabItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/ThisFirefoxItem.js
@@ -0,0 +1,15 @@
+/* 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 RuntimeItem = require("./RuntimeItem");
+
+class ThisFirefoxItem extends RuntimeItem {
+  renderComponents(runtime) {
+    return "This Firefox";
+  }
+}
+
+module.exports = ThisFirefoxItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/WorkerItem.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/. */
+
+"use strict";
+
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const DebugTargetItem = require("./DebugTargetItem");
+
+class WorkerItem extends DebugTargetItem {
+  renderComponents(debugTarget) {
+    return [
+      dom.img(
+        {
+          className: "icon",
+          src: "chrome://devtools/skin/images/debugging-workers.svg",
+        }
+      ),
+      dom.div(
+        {
+          className: "name",
+          title: debugTarget.name,
+        },
+        debugTarget.name
+      ),
+    ];
+  }
+}
+
+module.exports = WorkerItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/components/moz.build
@@ -0,0 +1,17 @@
+# 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(
+    'App.js',
+    'AppInfo.js',
+    'DebugPanel.js',
+    'DebugTargetItem.js',
+    'DebugTargetList.js',
+    'ExtensionItem.js',
+    'RuntimeItem.js',
+    'RuntimeList.js',
+    'TabItem.js',
+    'ThisFirefoxItem.js',
+    'WorkerItem.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/index.html
@@ -0,0 +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/. -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <link rel="stylesheet" href="chrome://devtools/content/aboutdebugging-ng/aboutdebugging.css"/>
+    <script src="chrome://devtools/content/aboutdebugging-ng/aboutdebugging.js"></script>
+  </head>
+  <body>
+    <div id="root"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/moz.build
@@ -0,0 +1,17 @@
+# 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 += [
+    'components',
+    'runtimes',
+]
+
+DevToolsModules(
+    'actions.js',
+    'reducer.js',
+    'store.js',
+)
+
+with Files('**'):
+    BUG_COMPONENT = ('DevTools', 'about:debugging')
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/reducer.js
@@ -0,0 +1,26 @@
+/* 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 {
+  UPDATE_SELECTED_RUNTIME,
+} = require("./actions");
+
+const INITIAL_STATE = {
+  selectedRuntime: null,
+};
+
+const reducers = {
+  [UPDATE_SELECTED_RUNTIME](state, { selectedRuntime }) {
+    return Object.assign({}, state, {
+      selectedRuntime,
+    });
+  },
+};
+
+module.exports = function(state = INITIAL_STATE, action) {
+  const reducer = reducers[action.type];
+  return reducer ? reducer(state, action) : state;
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/runtimes/Runtime.js
@@ -0,0 +1,10 @@
+/* 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";
+
+class Runtime {
+}
+
+module.exports = Runtime;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/runtimes/ThisFirefox.js
@@ -0,0 +1,50 @@
+/* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
+const { DebuggerServer } = require("devtools/server/main");
+const { DebuggerClient } = require("devtools/shared/client/debugger-client");
+
+const Runtime = require("./Runtime");
+
+class ThisFirefox extends Runtime {
+  constructor() {
+    super();
+  }
+
+  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 getAppInfo() {
+    return {
+      icon: "chrome://branding/content/icon64.png",
+      name: Services.appinfo.name,
+      version: Services.appinfo.version,
+    };
+  }
+
+  async getExtensions() {
+    const { addons } = await this.client.listAddons();
+    return addons;
+  }
+
+  async getTabs() {
+    const { tabs } = await this.client.listTabs({ favicons: true });
+    return tabs;
+  }
+
+  async getWorkers() {
+    return this.client.mainRoot.listAllWorkers();
+  }
+}
+
+module.exports = ThisFirefox;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/runtimes/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(
+    'Runtime.js',
+    'ThisFirefox.js',
+)
+
+with Files('**'):
+    BUG_COMPONENT = ('DevTools', 'about:debugging')
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-ng/store.js
@@ -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/. */
+
+"use strict";
+
+const createStore = require("devtools/client/shared/redux/create-store");
+const reducer = require("./reducer");
+const flags = require("devtools/shared/flags");
+
+module.exports = function() {
+  let shouldLog = false;
+  let history;
+
+  // If testing, store the action history in an array
+  // we'll later attach to the store
+  if (flags.testing) {
+    history = [];
+    shouldLog = true;
+  }
+
+  const store = createStore({
+    log: shouldLog,
+    history
+  })(reducer, {});
+
+  if (history) {
+    store.history = history;
+  }
+
+  return store;
+};
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -84,16 +84,19 @@ devtools.jar:
     content/framework/connect/connect.css (framework/connect/connect.css)
     content/framework/connect/connect.js (framework/connect/connect.js)
     content/shared/widgets/graphs-frame.xhtml (shared/widgets/graphs-frame.xhtml)
     content/shared/widgets/cubic-bezier.css (shared/widgets/cubic-bezier.css)
     content/shared/widgets/filter-widget.css (shared/widgets/filter-widget.css)
     content/shared/widgets/spectrum.css (shared/widgets/spectrum.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
+    content/aboutdebugging-ng/index.html (aboutdebugging-ng/index.html)
+    content/aboutdebugging-ng/aboutdebugging.css (aboutdebugging-ng/aboutdebugging.css)
+    content/aboutdebugging-ng/aboutdebugging.js (aboutdebugging-ng/aboutdebugging.js)
     content/responsive.html/index.xhtml (responsive.html/index.xhtml)
     content/dom/index.html (dom/index.html)
     content/dom/main.js (dom/main.js)
     content/accessibility/index.html (accessibility/index.html)
     content/accessibility/main.js (accessibility/main.js)
 %   skin devtools classic/1.0 %skin/
     skin/devtools-browser.css (themes/devtools-browser.css)
     skin/dark-theme.css (themes/dark-theme.css)
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 include('../templates.mozbuild')
 
 DIRS += [
     'aboutdebugging',
+    'aboutdebugging-ng',
     'accessibility',
     'application',
     'canvasdebugger',
     'commandline',
     'debugger',
     'dom',
     'framework',
     'inspector',
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -305,8 +305,15 @@ pref("devtools.editor.enableCodeFolding"
 pref("devtools.editor.autocomplete", true);
 
 // Whether to reload when touch simulation is toggled
 pref("devtools.responsive.reloadConditions.touchSimulation", false);
 // Whether to reload when user agent is changed
 pref("devtools.responsive.reloadConditions.userAgent", false);
 // Whether to show the notification about reloading to apply emulation
 pref("devtools.responsive.reloadNotification.enabled", true);
+
+// Enable about:debugging NG in Nightly only
+#if defined(NIGHTLY_BUILD)
+pref("devtools.aboutdebugging.ng-enabled", true);
+#else
+pref("devtools.aboutdebugging.ng-enabled", false);
+#endif
--- a/devtools/startup/aboutdebugging-registration.js
+++ b/devtools/startup/aboutdebugging-registration.js
@@ -11,26 +11,28 @@
 const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
 
 const { nsIAboutModule } = Ci;
 
 function AboutDebugging() {}
 
 AboutDebugging.prototype = {
-  uri: Services.io.newURI("chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml"),
   classDescription: "about:debugging",
   classID: Components.ID("1060afaf-dc9e-43da-8646-23a2faf48493"),
   contractID: "@mozilla.org/network/protocol/about;1?what=debugging",
 
   QueryInterface: ChromeUtils.generateQI([nsIAboutModule]),
 
-  newChannel: function(uri, loadInfo) {
+  newChannel: function(_, loadInfo) {
+    const uri = Services.prefs.getBoolPref("devtools.aboutdebugging.ng-enabled")
+                  ? "chrome://devtools/content/aboutdebugging-ng/index.html"
+                  : "chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml";
     const chan = Services.io.newChannelFromURIWithLoadInfo(
-      this.uri,
+      Services.io.newURI(uri),
       loadInfo
     );
     chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
     return chan;
   },
 
   getURIFlags: function(uri) {
     return nsIAboutModule.ALLOW_SCRIPT;