Bug 1363097 - Convert font inspector to React/Redux. r=gl draft
authorMike Park <mikeparkms@gmail.com>
Wed, 10 May 2017 14:57:05 -0400
changeset 585225 05ce1b65c5a49816ec80c0e12083aa9019f9562b
parent 585115 9104506c0a5eaac89b6c22c804c83ccee7d6e662
child 630678 43b1144130af546e224a891eb4eb63a92f308425
push id61060
push userbmo:mpark@mozilla.com
push dateFri, 26 May 2017 17:40:05 +0000
reviewersgl
bugs1363097
milestone55.0a1
Bug 1363097 - Convert font inspector to React/Redux. r=gl MozReview-Commit-ID: Du5ZqnnLaE1
devtools/client/inspector/fonts/actions/fonts.js
devtools/client/inspector/fonts/actions/index.js
devtools/client/inspector/fonts/actions/moz.build
devtools/client/inspector/fonts/actions/preview-text.js
devtools/client/inspector/fonts/actions/show-all-fonts.js
devtools/client/inspector/fonts/components/App.js
devtools/client/inspector/fonts/components/Font.js
devtools/client/inspector/fonts/components/FontList.js
devtools/client/inspector/fonts/components/moz.build
devtools/client/inspector/fonts/fonts.js
devtools/client/inspector/fonts/moz.build
devtools/client/inspector/fonts/reducers/fonts.js
devtools/client/inspector/fonts/reducers/moz.build
devtools/client/inspector/fonts/reducers/preview-text.js
devtools/client/inspector/fonts/reducers/show-all-fonts.js
devtools/client/inspector/fonts/test/browser_fontinspector.js
devtools/client/inspector/fonts/test/browser_fontinspector_edit-previews-show-all.js
devtools/client/inspector/fonts/test/browser_fontinspector_edit-previews.js
devtools/client/inspector/fonts/test/browser_fontinspector_theme-change.js
devtools/client/inspector/fonts/test/head.js
devtools/client/inspector/fonts/types.js
devtools/client/inspector/fonts/utils/l10n.js
devtools/client/inspector/fonts/utils/moz.build
devtools/client/inspector/inspector.js
devtools/client/inspector/inspector.xhtml
devtools/client/inspector/reducers.js
devtools/client/shared/browser-loader.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/actions/fonts.js
@@ -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/. */
+
+"use strict";
+
+const {
+  UPDATE_FONTS,
+} = require("./index");
+
+module.exports = {
+
+  /**
+   * Update the list of fonts in the font inspector
+   */
+  updateFonts(fonts) {
+    return {
+      type: UPDATE_FONTS,
+      fonts,
+    };
+  },
+
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/actions/index.js
@@ -0,0 +1,20 @@
+/* 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");
+
+createEnum([
+
+  // Update the list of fonts.
+  "UPDATE_FONTS",
+
+  // Update the preview test.
+  "UPDATE_PREVIEW_TEXT",
+
+  // Update whether to show all fonts.
+  "UPDATE_SHOW_ALL_FONTS",
+
+], module.exports);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/actions/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DevToolsModules(
+    'fonts.js',
+    'index.js',
+    'preview-text.js',
+    'show-all-fonts.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/actions/preview-text.js
@@ -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/. */
+
+"use strict";
+
+const {
+  UPDATE_PREVIEW_TEXT,
+} = require("./index");
+
+module.exports = {
+
+  /**
+   * Update the preview text in the font inspector
+   */
+  updatePreviewText(previewText) {
+    return {
+      type: UPDATE_PREVIEW_TEXT,
+      previewText,
+    };
+  },
+
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/actions/show-all-fonts.js
@@ -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/. */
+
+"use strict";
+
+const {
+  UPDATE_SHOW_ALL_FONTS,
+} = require("./index");
+
+module.exports = {
+
+  /**
+   * Update whether to show all fonts in the font inspector
+   */
+  updateShowAllFonts(showAllFonts) {
+    return {
+      type: UPDATE_SHOW_ALL_FONTS,
+      showAllFonts,
+    };
+  },
+
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/components/App.js
@@ -0,0 +1,86 @@
+/* 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 { addons, createClass, createFactory, DOM: dom, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
+
+const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
+const FontList = createFactory(require("./FontList"));
+
+const { getStr } = require("../utils/l10n");
+const Types = require("../types");
+
+const PREVIEW_UPDATE_DELAY = 150;
+
+const App = createClass({
+
+  displayName: "App",
+
+  propTypes: {
+    fonts: PropTypes.arrayOf(PropTypes.shape(Types.font)).isRequired,
+    previewText: PropTypes.string.isRequired,
+    showAllFonts: PropTypes.bool.isRequired,
+    onPreviewFonts: PropTypes.func.isRequired,
+    onShowAllFontClick: PropTypes.func.isRequired,
+    onTextBoxContextMenu: PropTypes.func,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  componentDidMount() {
+    let { onTextBoxContextMenu } = this.props;
+
+    let searchInput = findDOMNode(this).querySelector(".devtools-textinput");
+    searchInput.addEventListener("contextmenu", onTextBoxContextMenu);
+  },
+
+  componentWillUnmount() {
+    let { onTextBoxContextMenu } = this.props;
+
+    let searchInput = findDOMNode(this).querySelector(".devtools-textinput");
+    searchInput.removeEventListener("contextmenu", onTextBoxContextMenu);
+  },
+
+  render() {
+    let {
+      fonts,
+      onPreviewFonts,
+      onShowAllFontClick
+    } = this.props;
+
+    return dom.div(
+      {
+        className: "devtools-monospace theme-sidebar inspector-tabpanel",
+        id: "sidebar-panel-fontinspector"
+      },
+      dom.div(
+        {
+          className: "devtools-toolbar"
+        },
+        SearchBox({
+          delay: PREVIEW_UPDATE_DELAY,
+          placeholder: getStr("fontinspector.previewText"),
+          type: "text",
+          onChange: onPreviewFonts,
+        }),
+        dom.label(
+          {
+            id: "font-showall",
+            className: "theme-link",
+            title: getStr("fontinspector.seeAll.tooltip"),
+            onClick: onShowAllFontClick,
+          },
+          getStr("fontinspector.seeAll")
+        )
+      ),
+      FontList({ fonts })
+    );
+  }
+});
+
+module.exports = connect(state => state)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/components/Font.js
@@ -0,0 +1,153 @@
+/* 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 { addons, createClass, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
+
+const { getStr } = require("../utils/l10n");
+const Types = require("../types");
+
+module.exports = createClass({
+
+  displayName: "Font",
+
+  propTypes: PropTypes.shape(Types.font).isRequired,
+
+  mixins: [ addons.PureRenderMixin ],
+
+  getSectionClasses() {
+    let classes = ["font"];
+    classes.push((this.props.URI) ? "is-remote" : "is-local");
+
+    if (this.props.rule) {
+      classes.push("has-code");
+    }
+
+    return classes.join(" ");
+  },
+
+  renderFontPreview(previewUrl) {
+    return dom.div(
+      {
+        className: "font-preview-container",
+      },
+      dom.img(
+        {
+          className: "font-preview",
+          src: previewUrl
+        }
+      )
+    );
+  },
+
+  renderFontName(name) {
+    return dom.h1(
+      {
+        className: "font-name",
+      },
+      name
+    );
+  },
+
+  renderFontFormatURL(URI, format) {
+    return dom.p(
+      {
+        className: "font-format-url"
+      },
+      dom.input(
+        {
+          className: "font-url",
+          readOnly: "readonly",
+          value: URI
+        }
+      ),
+      " ",
+      format ?
+        dom.span(
+          {
+            className: "font-format"
+          },
+          format
+        )
+        :
+        dom.span(
+          {
+            className: "font-format",
+            hidden: "true"
+          },
+          format
+        )
+    );
+  },
+
+  renderFontCSS(cssFamilyName) {
+    return dom.p(
+      {
+        className: "font-css"
+      },
+      dom.span(
+        {},
+        getStr("fontinspector.usedAs")
+      ),
+      " \"",
+      dom.span(
+        {
+          className: "font-css-name"
+        },
+        cssFamilyName
+      ),
+      "\""
+    );
+  },
+
+  renderFontCSSCode(rule, ruleText) {
+    return dom.pre(
+      {
+        className: "font-css-code"
+      },
+      rule ? ruleText : null
+    );
+  },
+
+  render() {
+    let {
+      name,
+      cssFamilyName,
+      format,
+      URI,
+      rule,
+      ruleText,
+      previewUrl,
+    } = this.props;
+
+    return dom.section(
+      {
+        className: this.getSectionClasses(),
+      },
+      this.renderFontPreview(previewUrl),
+      dom.div(
+        {
+          className: "font-info",
+        },
+        this.renderFontName(name),
+        dom.span(
+          {
+            className: "font-is-local",
+          },
+          " " + getStr("fontinspector.system")
+        ),
+        dom.span(
+          {
+            className: "font-is-remote",
+          },
+          " " + getStr("fontinspector.remote")
+        ),
+        this.renderFontFormatURL(URI, format),
+        this.renderFontCSS(cssFamilyName),
+        this.renderFontCSSCode(rule, ruleText)
+      )
+    );
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/components/FontList.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/. */
+
+"use strict";
+
+const { addons, createClass, createFactory, DOM: dom, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+
+const Font = createFactory(require("./Font"));
+
+const Types = require("../types");
+
+module.exports = createClass({
+
+  displayName: "FontList",
+
+  propTypes: {
+    fonts: PropTypes.arrayOf(PropTypes.shape(Types.font)).isRequired
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  render() {
+    let { fonts } = this.props;
+
+    return dom.div(
+      {
+        id: "font-container"
+      },
+      dom.ul(
+        {
+          id: "all-fonts"
+        },
+        fonts.map(font => Font({
+          name: font.name,
+          cssFamilyName: font.CSSFamilyName,
+          format: font.format,
+          URI: font.URI,
+          rule: font.rule,
+          ruleText: font.ruleText,
+          previewUrl: font.previewUrl,
+        }))
+      )
+    );
+  },
+
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/components/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DevToolsModules(
+    'App.js',
+    'Font.js',
+    'FontList.js',
+)
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -1,250 +1,189 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 = require("Services");
+
+const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+
 const {gDevTools} = require("devtools/client/framework/devtools");
 
+const App = createFactory(require("./components/App"));
+
+const { LocalizationHelper } = require("devtools/shared/l10n");
+const INSPECTOR_L10N =
+  new LocalizationHelper("devtools/client/locales/inspector.properties");
+
 const DEFAULT_PREVIEW_TEXT = "Abc";
-const PREVIEW_UPDATE_DELAY = 150;
+
+const { updateFonts } = require("./actions/fonts");
+const { updatePreviewText } = require("./actions/preview-text");
+const { updateShowAllFonts } = require("./actions/show-all-fonts");
 
 const {Task} = require("devtools/shared/task");
 const {getColor} = require("devtools/client/shared/theme");
 
 function FontInspector(inspector, window) {
+  this.document = window.document;
   this.inspector = inspector;
   this.pageStyle = this.inspector.pageStyle;
-  this.chromeDoc = window.document;
-  this.init();
+  this.store = inspector.store;
+
+  this.update = this.update.bind(this);
+  this.onNewNode = this.onNewNode.bind(this);
+  this.onPreviewFonts = this.onPreviewFonts.bind(this);
+  this.onShowAllFontClick = this.onShowAllFontClick.bind(this);
+  this.onThemeChanged = this.onThemeChanged.bind(this);
 }
 
 FontInspector.prototype = {
-  init: function () {
-    this.update = this.update.bind(this);
-    this.onNewNode = this.onNewNode.bind(this);
-    this.onThemeChanged = this.onThemeChanged.bind(this);
+
+  init() {
+    if (!this.inspector) {
+      return;
+    }
+
+    let app = App({
+      onPreviewFonts: this.onPreviewFonts,
+      onShowAllFontClick: this.onShowAllFontClick,
+      onTextBoxContextMenu: this.inspector.onTextBoxContextMenu
+    });
+
+    let provider = createElement(Provider, {
+      store: this.store,
+      id: "fontinspector",
+      title: INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
+      key: "fontinspector",
+    }, app);
+
+    let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
+
+    this.inspector.addSidebarTab(
+      "fontinspector",
+      INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
+      provider,
+      defaultTab == "fontinspector"
+    );
+
     this.inspector.selection.on("new-node-front", this.onNewNode);
     this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
-    this.showAll = this.showAll.bind(this);
-    this.showAllLink = this.chromeDoc.getElementById("font-showall");
-    this.showAllLink.addEventListener("click", this.showAll);
-    this.previewTextChanged = this.previewTextChanged.bind(this);
-    this.previewInput = this.chromeDoc.getElementById("font-preview-text-input");
-    this.previewInput.addEventListener("input", this.previewTextChanged);
-    this.previewInput.addEventListener("contextmenu",
-      this.inspector.onTextBoxContextMenu);
 
     // Listen for theme changes as the color of the previews depend on the theme
     gDevTools.on("theme-switched", this.onThemeChanged);
 
-    this.update();
-  },
-
-  /**
-   * Is the fontinspector visible in the sidebar?
-   */
-  isActive: function () {
-    return this.inspector.sidebar &&
-           this.inspector.sidebar.getCurrentTabID() == "fontinspector";
+    this.store.dispatch(updatePreviewText(""));
+    this.store.dispatch(updateShowAllFonts(false));
+    this.update(false, "");
   },
 
   /**
-   * Remove listeners.
+   * Destruction function called when the inspector is destroyed. Removes event listeners
+   * and cleans up references.
    */
-  destroy: function () {
-    this.chromeDoc = null;
-    this.inspector.sidebar.off("fontinspector-selected", this.onNewNode);
+  destroy() {
     this.inspector.selection.off("new-node-front", this.onNewNode);
-    this.showAllLink.removeEventListener("click", this.showAll);
-    this.previewInput.removeEventListener("input", this.previewTextChanged);
-    this.previewInput.removeEventListener("contextmenu",
-      this.inspector.onTextBoxContextMenu);
-
+    this.inspector.sidebar.off("fontinspector-selected", this.onNewNode);
     gDevTools.off("theme-switched", this.onThemeChanged);
 
-    if (this._previewUpdateTimeout) {
-      clearTimeout(this._previewUpdateTimeout);
-    }
+    this.document = null;
+    this.inspector = null;
+    this.pageStyle = null;
+    this.store = null;
+  },
+
+  /**
+   * Returns true if the font inspector panel is visible, and false otherwise.
+   */
+  isPanelVisible() {
+    return this.inspector.sidebar &&
+           this.inspector.sidebar.getCurrentTabID() === "fontinspector";
   },
 
   /**
    * Selection 'new-node' event handler.
    */
-  onNewNode: function () {
-    if (this.isActive() &&
-        this.inspector.selection.isConnected() &&
-        this.inspector.selection.isElementNode()) {
-      this.undim();
+  onNewNode() {
+    if (this.isPanelVisible()) {
+      this.update(false);
+    }
+  },
+
+  /**
+   * Handler for the "theme-switched" event.
+   */
+  onThemeChanged(event, frame) {
+    if (frame === this.document.defaultView) {
       this.update();
-    } else {
-      this.dim();
     }
   },
 
   /**
-   * The text to use for previews. Returns either the value user has typed to
-   * the preview input or DEFAULT_PREVIEW_TEXT if the input is empty or contains
-   * only whitespace.
-   */
-  getPreviewText: function () {
-    let inputText = this.previewInput.value.trim();
-    if (inputText === "") {
-      return DEFAULT_PREVIEW_TEXT;
-    }
-
-    return inputText;
-  },
-
-  /**
-   * Preview input 'input' event handler.
+   * Handler for change in preview input.
    */
-  previewTextChanged: function () {
-    if (this._previewUpdateTimeout) {
-      clearTimeout(this._previewUpdateTimeout);
-    }
-
-    this._previewUpdateTimeout = setTimeout(() => {
-      this.update(this._lastUpdateShowedAllFonts);
-    }, PREVIEW_UPDATE_DELAY);
-  },
-
-  /**
-   * Callback for the theme-switched event.
-   */
-  onThemeChanged: function (event, frame) {
-    if (frame === this.chromeDoc.defaultView) {
-      this.update(this._lastUpdateShowedAllFonts);
-    }
+  onPreviewFonts(value) {
+    this.update(undefined, value);
   },
 
   /**
-   * Hide the font list. No node are selected.
+   * Handler for click on show all fonts button.
    */
-  dim: function () {
-    let panel = this.chromeDoc.getElementById("sidebar-panel-fontinspector");
-    panel.classList.add("dim");
-    this.clear();
-  },
-
-  /**
-   * Show the font list. A node is selected.
-   */
-  undim: function () {
-    let panel = this.chromeDoc.getElementById("sidebar-panel-fontinspector");
-    panel.classList.remove("dim");
+  onShowAllFontClick() {
+    this.update(true);
   },
 
-  /**
-   * Clears the font list.
-   */
-  clear: function () {
-    this.chromeDoc.querySelector("#all-fonts").innerHTML = "";
-  },
-
- /**
-  * Retrieve all the font info for the selected node and display it.
-  */
-  update: Task.async(function* (showAllFonts) {
+  update: Task.async(function* (showAllFonts = this.store.getState().showAllFonts,
+                                previewText = this.store.getState().previewText) {
     let node = this.inspector.selection.nodeFront;
-    let panel = this.chromeDoc.getElementById("sidebar-panel-fontinspector");
 
     if (!node ||
-        !this.isActive() ||
+        !this.isPanelVisible() ||
         !this.inspector.selection.isConnected() ||
-        !this.inspector.selection.isElementNode() ||
-        panel.classList.contains("dim")) {
+        !this.inspector.selection.isElementNode()) {
       return;
     }
 
-    this._lastUpdateShowedAllFonts = showAllFonts;
+    this.store.dispatch(updatePreviewText(previewText));
+    this.store.dispatch(updateShowAllFonts(showAllFonts));
 
     let options = {
       includePreviews: true,
-      previewText: this.getPreviewText(),
+      previewText: (previewText === "") ? DEFAULT_PREVIEW_TEXT : previewText,
       previewFillStyle: getColor("body-color")
     };
 
     let fonts = [];
     if (showAllFonts) {
       fonts = yield this.pageStyle.getAllUsedFontFaces(options)
                       .then(null, console.error);
     } else {
       fonts = yield this.pageStyle.getUsedFontFaces(node, options)
                       .then(null, console.error);
     }
 
     if (!fonts || !fonts.length) {
       // No fonts to display. Clear the previously shown fonts.
-      this.clear();
+      this.store.dispatch(updateFonts(fonts));
       return;
     }
 
     for (let font of fonts) {
       font.previewUrl = yield font.preview.data.string();
     }
 
     // in case we've been destroyed in the meantime
-    if (!this.chromeDoc) {
+    if (!this.document) {
       return;
     }
 
-    // Make room for the new fonts.
-    this.clear();
-
-    for (let font of fonts) {
-      this.render(font);
-    }
+    this.store.dispatch(updateFonts(fonts));
 
     this.inspector.emit("fontinspector-updated");
-  }),
-
-  /**
-   * Display the information of one font.
-   */
-  render: function (font) {
-    let s = this.chromeDoc.querySelector("#font-template > section");
-    s = s.cloneNode(true);
-
-    s.querySelector(".font-name").textContent = font.name;
-    s.querySelector(".font-css-name").textContent = font.CSSFamilyName;
-
-    if (font.URI) {
-      s.classList.add("is-remote");
-    } else {
-      s.classList.add("is-local");
-    }
-
-    let formatElem = s.querySelector(".font-format");
-    if (font.format) {
-      formatElem.textContent = font.format;
-    } else {
-      formatElem.hidden = true;
-    }
-
-    s.querySelector(".font-url").value = font.URI;
-
-    if (font.rule) {
-      // This is the @font-face{…} code.
-      let cssText = font.ruleText;
-
-      s.classList.add("has-code");
-      s.querySelector(".font-css-code").textContent = cssText;
-    }
-    let preview = s.querySelector(".font-preview");
-    preview.src = font.previewUrl;
-
-    this.chromeDoc.querySelector("#all-fonts").appendChild(s);
-  },
-
-  /**
-   * Show all fonts for the document (including iframes)
-   */
-  showAll: function () {
-    this.update(true);
-  },
+  })
 };
 
-exports.FontInspector = FontInspector;
+module.exports = FontInspector;
--- a/devtools/client/inspector/fonts/moz.build
+++ b/devtools/client/inspector/fonts/moz.build
@@ -1,11 +1,19 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # 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 += [
+    'actions',
+    'components',
+    'reducers',
+    'utils',
+]
+
 DevToolsModules(
     'fonts.js',
+    'types.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/reducers/fonts.js
@@ -0,0 +1,27 @@
+/* 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_FONTS,
+} = require("../actions/index");
+
+const INITIAL_FONTS = [];
+
+let reducers = {
+
+  [UPDATE_FONTS](_, { fonts }) {
+    return fonts;
+  },
+
+};
+
+module.exports = function (fonts = INITIAL_FONTS, action) {
+  let reducer = reducers[action.type];
+  if (!reducer) {
+    return fonts;
+  }
+  return reducer(fonts, action);
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/reducers/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DevToolsModules(
+    'fonts.js',
+    'preview-text.js',
+    'show-all-fonts.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/reducers/preview-text.js
@@ -0,0 +1,27 @@
+/* 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_PREVIEW_TEXT,
+} = require("../actions/index");
+
+const INITIAL_PREVIEW_TEXT = "";
+
+let reducers = {
+
+  [UPDATE_PREVIEW_TEXT](_, { previewText }) {
+    return previewText;
+  },
+
+};
+
+module.exports = function (previewText = INITIAL_PREVIEW_TEXT, action) {
+  let reducer = reducers[action.type];
+  if (!reducer) {
+    return previewText;
+  }
+  return reducer(previewText, action);
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/reducers/show-all-fonts.js
@@ -0,0 +1,27 @@
+/* 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_SHOW_ALL_FONTS,
+} = require("../actions/index");
+
+const INITIAL_SHOW_ALL_FONTS = false;
+
+let reducers = {
+
+  [UPDATE_SHOW_ALL_FONTS](_, { showAllFonts }) {
+    return showAllFonts;
+  },
+
+};
+
+module.exports = function (showAllFonts = INITIAL_SHOW_ALL_FONTS, action) {
+  let reducer = reducers[action.type];
+  if (!reducer) {
+    return showAllFonts;
+  }
+  return reducer(showAllFonts, action);
+};
--- a/devtools/client/inspector/fonts/test/browser_fontinspector.js
+++ b/devtools/client/inspector/fonts/test/browser_fontinspector.js
@@ -31,17 +31,17 @@ const FONTS = [{
   format: "",
   cssName: "barnormal"
 }];
 
 add_task(function* () {
   let { inspector, view } = yield openFontInspectorForURL(TEST_URI);
   ok(!!view, "Font inspector document is alive.");
 
-  let viewDoc = view.chromeDoc;
+  let viewDoc = view.document;
 
   yield testBodyFonts(inspector, viewDoc);
   yield testDivFonts(inspector, viewDoc);
   yield testShowAllFonts(inspector, viewDoc);
 });
 
 function* testBodyFonts(inspector, viewDoc) {
   let s = viewDoc.querySelectorAll("#all-fonts > section");
--- a/devtools/client/inspector/fonts/test/browser_fontinspector_edit-previews-show-all.js
+++ b/devtools/client/inspector/fonts/test/browser_fontinspector_edit-previews-show-all.js
@@ -5,17 +5,17 @@
 
 // Test that correct previews are shown if the text is edited after 'Show all'
 // button is pressed.
 
 const TEST_URI = URL_ROOT + "browser_fontinspector.html";
 
 add_task(function* () {
   let { inspector, view } = yield openFontInspectorForURL(TEST_URI);
-  let viewDoc = view.chromeDoc;
+  let viewDoc = view.document;
 
   info("Selecting a node that doesn't contain all document fonts.");
   yield selectNode(".normal-text", inspector);
 
   let normalTextNumPreviews =
     viewDoc.querySelectorAll("#all-fonts .font-preview").length;
 
   let onUpdated = inspector.once("fontinspector-updated");
--- a/devtools/client/inspector/fonts/test/browser_fontinspector_edit-previews.js
+++ b/devtools/client/inspector/fonts/test/browser_fontinspector_edit-previews.js
@@ -6,17 +6,17 @@
 // Test that previews change when the preview text changes. It doesn't check the
 // exact preview images because they are drawn on a canvas causing them to vary
 // between systems, platforms and software versions.
 
 const TEST_URI = URL_ROOT + "browser_fontinspector.html";
 
 add_task(function* () {
   let {view} = yield openFontInspectorForURL(TEST_URI);
-  let viewDoc = view.chromeDoc;
+  let viewDoc = view.document;
 
   let previews = viewDoc.querySelectorAll("#all-fonts .font-preview");
   let initialPreviews = [...previews].map(p => p.src);
 
   info("Typing 'Abc' to check that the reference previews are correct.");
   yield updatePreviewText(view, "Abc");
   checkPreviewImages(viewDoc, initialPreviews, true);
 
--- a/devtools/client/inspector/fonts/test/browser_fontinspector_theme-change.js
+++ b/devtools/client/inspector/fonts/test/browser_fontinspector_theme-change.js
@@ -14,17 +14,17 @@ const originalTheme = getTheme();
 
 registerCleanupFunction(() => {
   info(`Restoring theme to '${originalTheme}.`);
   setTheme(originalTheme);
 });
 
 add_task(function* () {
   let { inspector, view } = yield openFontInspectorForURL(TEST_URI);
-  let { chromeDoc: doc } = view;
+  let { document: doc } = view;
 
   yield selectNode(".normal-text", inspector);
 
   // Store the original preview URI for later comparison.
   let originalURI = doc.querySelector("#all-fonts .font-preview").src;
   let newTheme = originalTheme === "light" ? "dark" : "light";
 
   info(`Original theme was '${originalTheme}'.`);
--- a/devtools/client/inspector/fonts/test/head.js
+++ b/devtools/client/inspector/fonts/test/head.js
@@ -56,18 +56,18 @@ var openFontInspectorForURL = Task.async
  * preview images to be updated.
  *
  * @param {FontInspector} view - The FontInspector instance.
  * @param {String} text - The text to preview.
  */
 function* updatePreviewText(view, text) {
   info(`Changing the preview text to '${text}'`);
 
-  let doc = view.chromeDoc;
-  let input = doc.getElementById("font-preview-text-input");
+  let doc = view.document;
+  let input = doc.querySelector("#sidebar-panel-fontinspector .devtools-textinput");
   let update = view.inspector.once("fontinspector-updated");
 
   info("Focusing the input field.");
   input.focus();
 
   is(doc.activeElement, input, "The input was focused.");
 
   info("Blanking the input field.");
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/types.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 { PropTypes } = require("devtools/client/shared/vendor/react");
+
+/**
+ * A single font.
+ */
+exports.font = {
+
+  // The name of the font
+  name: PropTypes.string,
+
+  // The name of the font family
+  cssFamilyName: PropTypes.string,
+
+  // The format of the font
+  format: PropTypes.string,
+
+  // The URI of the font file
+  URI: PropTypes.string,
+
+  // Object containing the CSS rule for the font
+  rule: PropTypes.object,
+
+  // The text of the CSS rule
+  ruleText: PropTypes.string,
+
+  // URL for the font preview
+  previewUrl: PropTypes.string,
+
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/utils/l10n.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 { LocalizationHelper } = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/client/locales/font-inspector.properties");
+
+module.exports = {
+  getStr: (...args) => L10N.getStr(...args),
+  getFormatStr: (...args) => L10N.getFormatStr(...args),
+  getFormatStrWithNumbers: (...args) => L10N.getFormatStrWithNumbers(...args),
+  numberWithDecimals: (...args) => L10N.numberWithDecimals(...args),
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/fonts/utils/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DevToolsModules(
+    'l10n.js',
+)
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -572,17 +572,17 @@ Inspector.prototype = {
         break;
       case "boxmodel":
         // box-model isn't a panel on its own, it used to, now it is being used by
         // computed view and layout which retrieves an instance via getPanel.
         const BoxModel = require("devtools/client/inspector/boxmodel/box-model");
         panel = new BoxModel(this, this.panelWin);
         break;
       case "fontinspector":
-        const {FontInspector} = require("devtools/client/inspector/fonts/fonts");
+        const FontInspector = require("devtools/client/inspector/fonts/fonts");
         panel = new FontInspector(this, this.panelWin);
         break;
       default:
         // This is a custom panel or a non lazy-loaded one.
         return null;
     }
     this._panels.set(id, panel);
     return panel;
@@ -630,20 +630,19 @@ Inspector.prototype = {
         "animationinspector",
         INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle"),
         "chrome://devtools/content/animationinspector/animation-inspector.xhtml",
         defaultTab == "animationinspector");
     }
 
     if (Services.prefs.getBoolPref("devtools.fontinspector.enabled") &&
         this.canGetUsedFontFaces) {
-      this.sidebar.addExistingTab(
-        "fontinspector",
-        INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
-        defaultTab == "fontinspector");
+      const FontInspector = this.browserRequire("devtools/client/inspector/fonts/fonts");
+      this.fontinspector = new FontInspector(this, this.panelWin);
+      this.fontinspector.init();
 
       this.sidebar.toggleTab(true, "fontinspector");
     }
 
     // Setup the splitter before the sidebar is displayed so,
     // we don't miss any events.
     this.setupSplitter();
 
@@ -970,16 +969,20 @@ Inspector.prototype = {
     if (this.gridInspector) {
       this.gridInspector.destroy();
     }
 
     if (this.layoutview) {
       this.layoutview.destroy();
     }
 
+    if (this.fontinspector) {
+      this.fontinspector.destroy();
+    }
+
     let cssPropertiesDestroyer = this._cssPropertiesLoaded.then(({front}) => {
       if (front) {
         front.destroy();
       }
     });
 
     this.sidebar.off("select", this.onSidebarSelect);
     let sidebarDestroyer = this.sidebar.destroy();
--- a/devtools/client/inspector/inspector.xhtml
+++ b/devtools/client/inspector/inspector.xhtml
@@ -140,54 +140,16 @@
             <div id="propertyContainer" class="theme-separator" tabindex="0" dir="ltr">
             </div>
 
             <div id="computedview-no-results" hidden="" data-localization="content=inspector.noProperties"></div>
           </div>
         </div>
       </div>
 
-      <div id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel"
-                data-localization-bundle="devtools/client/locales/font-inspector.properties">
-        <div class="devtools-toolbar">
-          <div class="devtools-searchbox">
-            <input id="font-preview-text-input" class="devtools-textinput" type="search"
-                        data-localization="placeholder=fontinspector.previewText"/>
-          </div>
-          <label id="font-showall" class="theme-link"
-                      data-localization="content=fontinspector.seeAll;
-                                         title=fontinspector.seeAll.tooltip"></label>
-        </div>
-
-        <div id="font-container">
-          <ul id="all-fonts"></ul>
-        </div>
-
-        <div id="font-template">
-          <section class="font">
-            <div class="font-preview-container">
-              <img class="font-preview"></img>
-            </div>
-            <div class="font-info">
-              <h1 class="font-name"></h1>
-              <span class="font-is-local" data-localization="content=fontinspector.system"></span>
-              <span class="font-is-remote" data-localization="content=fontinspector.remote"></span>
-              <p class="font-format-url">
-                <input readonly="readonly" class="font-url"></input>
-                <span class="font-format"></span>
-              </p>
-              <p class="font-css">
-                <span data-localization="content=fontinspector.usedAs"></span> "<span class="font-css-name"></span>"
-              </p>
-              <pre class="font-css-code"></pre>
-            </div>
-          </section>
-        </div>
-      </div>
-
       <div id="sidebar-panel-animationinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
         <iframe class="devtools-inspector-tab-frame"></iframe>
       </div>
     </div>
 
   </div>
 </body>
 </html>
--- a/devtools/client/inspector/reducers.js
+++ b/devtools/client/inspector/reducers.js
@@ -3,10 +3,13 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // This file exposes the Redux reducers of the box model, grid and grid highlighter
 // settings.
 
 exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
+exports.fonts = require("devtools/client/inspector/fonts/reducers/fonts");
 exports.grids = require("devtools/client/inspector/grids/reducers/grids");
 exports.highlighterSettings = require("devtools/client/inspector/grids/reducers/highlighter-settings");
+exports.previewText = require("devtools/client/inspector/fonts/reducers/preview-text");
+exports.showAllFonts = require("devtools/client/inspector/fonts/reducers/show-all-fonts");
--- a/devtools/client/shared/browser-loader.js
+++ b/devtools/client/shared/browser-loader.js
@@ -9,16 +9,17 @@ const { devtools } = Cu.import("resource
 const { joinURI } = devtools.require("devtools/shared/path");
 const { assert } = devtools.require("devtools/shared/DevToolsUtils");
 const Services = devtools.require("Services");
 const { AppConstants } = devtools.require("resource://gre/modules/AppConstants.jsm");
 
 const BROWSER_BASED_DIRS = [
   "resource://devtools/client/inspector/boxmodel",
   "resource://devtools/client/inspector/computed",
+  "resource://devtools/client/inspector/fonts",
   "resource://devtools/client/inspector/grids",
   "resource://devtools/client/inspector/layout",
   "resource://devtools/client/jsonview",
   "resource://devtools/client/shared/source-map",
   "resource://devtools/client/shared/redux",
   "resource://devtools/client/shared/vendor",
 ];