Bug 1201475 - Implement DOM Panel; r=linclark, bgrins, helenvholmes
authorJan Odvarko <odvarko@gmail.com>
Tue, 09 Feb 2016 23:23:29 +0100
changeset 332345 6f83467f1a10cdee1f9b596e7b368b71cc601f2c
parent 332344 80598be5d80e695727ff74e712faa623b02f3c70
child 332346 60df1f524d7f3303c20a289f25d546dc18bbac8e
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslinclark, bgrins, helenvholmes
bugs1201475
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1201475 - Implement DOM Panel; r=linclark, bgrins, helenvholmes MozReview-Commit-ID: IMYSW7ObOSh
devtools/client/definitions.js
devtools/client/dom/.eslintrc
devtools/client/dom/content/actions/filter.js
devtools/client/dom/content/actions/grips.js
devtools/client/dom/content/actions/moz.build
devtools/client/dom/content/components/dom-tree.js
devtools/client/dom/content/components/main-frame.js
devtools/client/dom/content/components/main-toolbar.js
devtools/client/dom/content/components/moz.build
devtools/client/dom/content/components/search-box.css
devtools/client/dom/content/components/search-box.js
devtools/client/dom/content/components/search.svg
devtools/client/dom/content/constants.js
devtools/client/dom/content/dom-decorator.js
devtools/client/dom/content/dom-view.css
devtools/client/dom/content/dom-view.js
devtools/client/dom/content/grip-provider.js
devtools/client/dom/content/moz.build
devtools/client/dom/content/reducers/filter.js
devtools/client/dom/content/reducers/grips.js
devtools/client/dom/content/reducers/index.js
devtools/client/dom/content/reducers/moz.build
devtools/client/dom/content/utils.js
devtools/client/dom/dom-panel.js
devtools/client/dom/dom.html
devtools/client/dom/main.js
devtools/client/dom/moz.build
devtools/client/dom/test/browser.ini
devtools/client/dom/test/head.js
devtools/client/jar.mn
devtools/client/locales/en-US/dom.properties
devtools/client/moz.build
devtools/client/shared/components/reps/attribute.js
devtools/client/shared/components/reps/date-time.js
devtools/client/shared/components/reps/document.js
devtools/client/shared/components/reps/event.js
devtools/client/shared/components/reps/function.js
devtools/client/shared/components/reps/grip-array.js
devtools/client/shared/components/reps/grip.js
devtools/client/shared/components/reps/moz.build
devtools/client/shared/components/reps/named-node-map.js
devtools/client/shared/components/reps/object-with-text.js
devtools/client/shared/components/reps/object-with-url.js
devtools/client/shared/components/reps/object.js
devtools/client/shared/components/reps/regexp.js
devtools/client/shared/components/reps/rep-utils.js
devtools/client/shared/components/reps/rep.js
devtools/client/shared/components/reps/string.js
devtools/client/shared/components/reps/stylesheet.js
devtools/client/shared/components/reps/text-node.js
devtools/client/shared/components/reps/url.js
devtools/client/shared/components/reps/window.js
devtools/client/themes/dom.css
devtools/client/themes/images/emojis/emoji-tool-dom.svg
devtools/client/themes/images/tool-dom.svg
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -18,45 +18,48 @@ loader.lazyGetter(this, "StyleEditorPane
 loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/client/shadereditor/panel").ShaderEditorPanel);
 loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/client/canvasdebugger/panel").CanvasDebuggerPanel);
 loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/client/webaudioeditor/panel").WebAudioEditorPanel);
 loader.lazyGetter(this, "MemoryPanel", () => require("devtools/client/memory/panel").MemoryPanel);
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
+loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
 
 // Strings
 const toolboxProps = "chrome://devtools/locale/toolbox.properties";
 const inspectorProps = "chrome://devtools/locale/inspector.properties";
 const webConsoleProps = "chrome://devtools/locale/webconsole.properties";
 const debuggerProps = "chrome://devtools/locale/debugger.properties";
 const styleEditorProps = "chrome://devtools/locale/styleeditor.properties";
 const shaderEditorProps = "chrome://devtools/locale/shadereditor.properties";
 const canvasDebuggerProps = "chrome://devtools/locale/canvasdebugger.properties";
 const webAudioEditorProps = "chrome://devtools/locale/webaudioeditor.properties";
 const performanceProps = "chrome://devtools/locale/performance.properties";
 const netMonitorProps = "chrome://devtools/locale/netmonitor.properties";
 const storageProps = "chrome://devtools/locale/storage.properties";
 const scratchpadProps = "chrome://devtools/locale/scratchpad.properties";
 const memoryProps = "chrome://devtools/locale/memory.properties";
+const domProps = "chrome://devtools/locale/dom.properties";
 
 loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps));
 loader.lazyGetter(this, "performanceStrings", () => Services.strings.createBundle(performanceProps));
 loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
 loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
 loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps));
 loader.lazyGetter(this, "shaderEditorStrings", () => Services.strings.createBundle(shaderEditorProps));
 loader.lazyGetter(this, "canvasDebuggerStrings", () => Services.strings.createBundle(canvasDebuggerProps));
 loader.lazyGetter(this, "webAudioEditorStrings", () => Services.strings.createBundle(webAudioEditorProps));
 loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps));
 loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps));
 loader.lazyGetter(this, "storageStrings", () => Services.strings.createBundle(storageProps));
 loader.lazyGetter(this, "scratchpadStrings", () => Services.strings.createBundle(scratchpadProps));
 loader.lazyGetter(this, "memoryStrings", () => Services.strings.createBundle(memoryProps));
+loader.lazyGetter(this, "domStrings", () => Services.strings.createBundle(domProps));
 
 var Tools = {};
 exports.Tools = Tools;
 
 // Definitions
 Tools.options = {
   id: "options",
   ordinal: 0,
@@ -392,30 +395,58 @@ Tools.scratchpad = {
     return target.hasActor("console");
   },
 
   build: function(iframeWindow, toolbox) {
     return new ScratchpadPanel(iframeWindow, toolbox);
   }
 };
 
+Tools.dom = {
+  id: "dom",
+  accesskey: l10n("dom.accesskey", domStrings),
+  key: l10n("dom.commandkey", domStrings),
+  ordinal: 13,
+  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
+  visibilityswitch: "devtools.dom.enabled",
+  icon: "chrome://devtools/skin/images/tool-dom.svg",
+  invertIconForLightTheme: true,
+  url: "chrome://devtools/content/dom/dom.html",
+  label: l10n("dom.label", domStrings),
+  panelLabel: l10n("dom.panelLabel", domStrings),
+  get tooltip() {
+    return l10n("dom.tooltip", domStrings,
+    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
+  },
+  inMenu: true,
+
+  isTargetSupported: function(target) {
+    return target.getTrait("webConsoleCommands");
+  },
+
+  build: function(iframeWindow, toolbox) {
+    return new DomPanel(iframeWindow, toolbox);
+  }
+};
+
 var defaultTools = [
   Tools.options,
   Tools.webConsole,
   Tools.inspector,
   Tools.jsdebugger,
   Tools.styleEditor,
   Tools.shaderEditor,
   Tools.canvasDebugger,
   Tools.webAudioEditor,
   Tools.performance,
   Tools.netMonitor,
   Tools.storage,
   Tools.scratchpad,
   Tools.memory,
+  Tools.dom,
 ];
 
 exports.defaultTools = defaultTools;
 
 Tools.darkTheme = {
   id: "dark",
   label: l10n("options.darkTheme.label2", toolboxStrings),
   ordinal: 1,
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/.eslintrc
@@ -0,0 +1,15 @@
+{
+  "globals": {
+    "XMLHttpRequest": true,
+    "window": true,
+    "define": true,
+    "addEventListener": true,
+    "document": true,
+    "dispatchEvent": true,
+    "MessageEvent": true
+  },
+  "rules": {
+    "indent": 0,
+    "padded-blocks": 0,
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/actions/filter.js
@@ -0,0 +1,21 @@
+/* -*- 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 constants = require("../constants");
+
+/**
+ * Used to filter DOM panel content.
+ */
+function setVisibilityFilter(filter) {
+  return {
+    filter: filter,
+    type: constants.SET_VISIBILITY_FILTER,
+  };
+}
+
+// Exports from this module
+exports.setVisibilityFilter = setVisibilityFilter;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/actions/grips.js
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+ /* globals DomProvider */
+"use strict";
+
+const constants = require("../constants");
+
+/**
+ * Used to fetch grip prototype and properties from the backend.
+ */
+function requestProperties(grip) {
+  return {
+    grip: grip,
+    type: constants.FETCH_PROPERTIES,
+    status: "start",
+    error: false
+  };
+}
+
+/**
+ * Executed when grip properties are received from the backend.
+ */
+function receiveProperties(grip, response, error) {
+  return {
+    grip: grip,
+    type: constants.FETCH_PROPERTIES,
+    status: "end",
+    response: response,
+    error: error
+  };
+}
+
+/**
+ * Used to get properties from the backend and fire an action
+ * when they are received.
+ */
+function fetchProperties(grip) {
+  return dispatch => {
+    // dispatch(requestProperties(grip));
+
+    // Use 'DomProvider' object exposed from the chrome scope.
+    return DomProvider.getPrototypeAndProperties(grip).then(response => {
+      dispatch(receiveProperties(grip, response));
+    });
+  };
+}
+
+// Exports from this module
+exports.requestProperties = requestProperties;
+exports.receiveProperties = receiveProperties;
+exports.fetchProperties = fetchProperties;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/actions/moz.build
@@ -0,0 +1,9 @@
+# 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(
+    'filter.js',
+    'grips.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/dom-tree.js
@@ -0,0 +1,90 @@
+/* -*- 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";
+
+// React & Redux
+const React = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+// Reps
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
+const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+const { Grip } = require("devtools/client/shared/components/reps/grip");
+
+// DOM Panel
+const { GripProvider } = require("../grip-provider");
+const { DomDecorator } = require("../dom-decorator");
+
+// Shortcuts
+const PropTypes = React.PropTypes;
+
+/**
+ * Renders DOM panel tree.
+ */
+var DomTree = React.createClass({
+  propTypes: {
+    object: PropTypes.any,
+    filter: PropTypes.string,
+    dispatch: PropTypes.func.isRequired,
+    grips: PropTypes.object,
+  },
+
+  displayName: "DomTree",
+
+  /**
+   * Filter DOM properties. Return true if the object
+   * should be visible in the tree.
+   */
+  onFilter: function(object) {
+    if (!this.props.filter) {
+      return true;
+    }
+
+    return (object.name && object.name.indexOf(this.props.filter) > -1);
+  },
+
+  /**
+   * Render DOM panel content
+   */
+  render: function() {
+    let columns = [{
+      "id": "value"
+    }];
+
+    // This is the integration point with Reps. The DomTree is using
+    // Reps to render all values. The code also specifies default rep
+    // used for data types that don't have its own specific template.
+    let renderValue = props => {
+      return Rep(Object.assign({}, props, {
+        defaultRep: Grip,
+      }));
+    };
+
+    return (
+      TreeView({
+        object: this.props.object,
+        provider: new GripProvider(this.props.grips, this.props.dispatch),
+        decorator: new DomDecorator(),
+        mode: "short",
+        columns: columns,
+        renderValue: renderValue,
+        onFilter: this.onFilter
+      })
+    );
+  }
+});
+
+const mapStateToProps = (state) => {
+  return {
+    grips: state.grips,
+    filter: state.filter
+  };
+};
+
+// Exports from this module
+module.exports = connect(mapStateToProps)(DomTree);
+
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/main-frame.js
@@ -0,0 +1,60 @@
+/* -*- 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";
+
+// React & Redux
+const React = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+// DOM Panel
+const DomTree = React.createFactory(require("./dom-tree"));
+const MainToolbar = React.createFactory(require("./main-toolbar"));
+
+// Shortcuts
+const { div } = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * Renders basic layout of the DOM panel. The DOM panel cotent consists
+ * from two main parts: toolbar and tree.
+ */
+var MainFrame = React.createClass({
+  propTypes: {
+    object: PropTypes.any,
+    filter: PropTypes.string,
+    dispatch: PropTypes.func.isRequired,
+  },
+
+  displayName: "MainFrame",
+
+  /**
+   * Render DOM panel content
+   */
+  render: function() {
+    return (
+      div({className: "mainFrame"},
+        MainToolbar({
+          dispatch: this.props.dispatch
+        }),
+        DomTree({
+          object: this.props.object,
+          filter: this.props.filter,
+        })
+      )
+    );
+  }
+});
+
+// Transform state into props
+// Note: use https://github.com/faassen/reselect for better performance.
+const mapStateToProps = (state) => {
+  return {
+    filter: state.filter
+  };
+};
+
+// Exports from this module
+module.exports = connect(mapStateToProps)(MainFrame);
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/main-toolbar.js
@@ -0,0 +1,63 @@
+/* -*- 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";
+
+// React
+const React = require("devtools/client/shared/vendor/react");
+const { l10n } = require("../utils");
+
+// Reps
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/toolbar"));
+
+// DOM Panel
+const SearchBox = React.createFactory(require("../components/search-box"));
+
+// Actions
+const { fetchProperties } = require("../actions/grips");
+const { setVisibilityFilter } = require("../actions/filter");
+
+// Shortcuts
+const PropTypes = React.PropTypes;
+
+/**
+ * This template is responsible for rendering a toolbar
+ * within the 'Headers' panel.
+ */
+var MainToolbar = React.createClass({
+  propTypes: {
+    object: PropTypes.any,
+    dispatch: PropTypes.func.isRequired,
+  },
+
+  displayName: "MainToolbar",
+
+  onRefresh: function() {
+    this.props.dispatch(fetchProperties(this.props.object));
+  },
+
+  onSearch: function(value) {
+    this.props.dispatch(setVisibilityFilter(value));
+  },
+
+  render: function() {
+    return (
+      Toolbar({},
+        ToolbarButton({
+          className: "btn copy",
+          onClick: this.onRefresh},
+          l10n.getStr("dom.refresh")
+        ),
+        SearchBox({
+          onSearch: this.onSearch
+        })
+      )
+    );
+  }
+});
+
+// Exports from this module
+module.exports = MainToolbar;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/moz.build
@@ -0,0 +1,13 @@
+# 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(
+    'dom-tree.js',
+    'main-frame.js',
+    'main-toolbar.js',
+    'search-box.css',
+    'search-box.js',
+    'search.svg',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/search-box.css
@@ -0,0 +1,46 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/******************************************************************************/
+/* Search Box */
+
+.searchBox {
+  height: 18px;
+  font-size: 12px;
+  margin-top: 0;
+  border: 1px solid rgb(170, 188, 207);
+  width: 200px;
+  float: right;
+  background-image: url("./search.svg");
+  background-repeat: no-repeat;
+  background-position: 2px center;
+  padding-left: 20px;
+  margin-right: 5px;
+}
+
+/******************************************************************************/
+/* Light Theme & Dark Theme*/
+
+.theme-dark .searchBox,
+.theme-light .searchBox {
+  border: 1px solid rgb(170, 170, 170);
+  background-image: url("chrome://devtools/skin/images/magnifying-glass-light.png");
+  background-position: 8px center;
+  border-radius: 2px;
+  padding-left: 25px;
+  margin-top: 1px;
+  height: 16px;
+  font-style: italic;
+}
+
+/******************************************************************************/
+/* Dark Theme */
+
+.theme-dark .searchBox {
+  background-color: rgba(24, 29, 32, 1);
+  color: rgba(184, 200, 217, 1);
+  border-color: var(--theme-splitter-color);
+  background-image: url("chrome://devtools/skin/images/magnifying-glass.png");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/search-box.js
@@ -0,0 +1,65 @@
+/* -*- 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 React = require("devtools/client/shared/vendor/react");
+const { l10n } = require("../utils");
+
+// For smooth incremental searching (in case the user is typing quickly).
+const searchDelay = 250;
+
+// Shortcuts
+const { input } = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This object represents a search box located at the
+ * top right corner of the application.
+ */
+var SearchBox = React.createClass({
+  propTypes: {
+    onSearch: PropTypes.func,
+  },
+
+  displayName: "SearchBox",
+
+  componentWillUnmount: function() {
+    // Clean up an existing timeout.
+    if (this.searchTimeout) {
+      window.clearTimeout(this.searchTimeout);
+    }
+  },
+
+  onSearch: function(event) {
+    let searchBox = event.target;
+
+    // Clean up an existing timeout before creating a new one.
+    if (this.searchTimeout) {
+      window.clearTimeout(this.searchTimeout);
+    }
+
+    // Execute the search after a timeout. It makes the UX
+    // smoother if the user is typing quickly.
+    this.searchTimeout = window.setTimeout(() => {
+      this.searchTimeout = null;
+      this.props.onSearch(searchBox.value);
+    }, searchDelay);
+  },
+
+  render: function() {
+    return (
+      input({
+        className: "searchBox",
+        placeholder: l10n.getStr("dom.filterDOMPanel"),
+        onChange: this.onSearch
+      })
+    );
+  }
+});
+
+// Exports from this module
+module.exports = SearchBox;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/search.svg
@@ -0,0 +1,22 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#427dc2"/>
+      <stop offset="1" stop-color="#5e9fce"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#2f5d93"/>
+      <stop offset="1" stop-color="#3a87bd"/>
+    </linearGradient>
+    <filter id="c" width="1.239" height="1.241" x="-.12" y="-.12" color-interpolation-filters="sRGB">
+      <feGaussianBlur stdDeviation=".637"/>
+    </filter>
+    <linearGradient id="d" x1="4.094" x2="4.094" y1="13.423" y2="2.743" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
+    <linearGradient id="e" x1="8.711" x2="8.711" y1="13.58" y2="2.566" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path fill="#fff" stroke="#fff" stroke-width="1.5" d="M10.14 1.656c-2.35 0-4.25 1.9-4.25 4.25 0 .752.19 1.45.532 2.063L1.61 12.78l1.562 1.564 4.78-4.78c.64.384 1.387.592 2.19.592 2.35 0 4.25-1.9 4.25-4.25s-1.9-4.25-4.25-4.25zm0 1.532c1.504 0 2.72 1.214 2.72 2.718s-1.216 2.72-2.72 2.72c-1.503 0-2.718-1.216-2.718-2.72 0-1.504 1.215-2.718 2.72-2.718z" stroke-linejoin="round" filter="url(#c)"/>
+  <path fill="url(#d)" stroke="url(#e)" stroke-width=".6" d="M10 2C7.79 2 6 3.79 6 6c0 .828.256 1.612.688 2.25l-4.875 4.875 1.062 1.063L7.75 9.31C8.388 9.745 9.172 10 10 10c2.21 0 4-1.79 4-4s-1.79-4-4-4zm0 1c1.657 0 3 1.343 3 3s-1.343 3-3 3-3-1.343-3-3 1.343-3 3-3z" stroke-linejoin="round"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/constants.js
@@ -0,0 +1,9 @@
+/* -*- 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";
+
+exports.FETCH_PROPERTIES = "FETCH_PROPERTIES";
+exports.SET_VISIBILITY_FILTER = "SET_VISIBILITY_FILTER";
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/dom-decorator.js
@@ -0,0 +1,48 @@
+/* -*- 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 { Property } = require("./reducers/grips");
+
+// Implementation
+
+function DomDecorator() {
+}
+
+/**
+ * Decorator for DOM panel tree component. It's responsible for
+ * appending an icon to read only properties.
+ */
+DomDecorator.prototype = {
+  getRowClass: function(object) {
+    if (object instanceof Property) {
+      let value = object.value;
+      let names = [];
+
+      if (value.enumerable) {
+        names.push("enumerable");
+      }
+      if (value.writable) {
+        names.push("writable");
+      }
+      if (value.configurable) {
+        names.push("configurable");
+      }
+
+      return names;
+    }
+  },
+
+  /**
+   * Return custom React template for specified object. The template
+   * might depend on specified column.
+   */
+  getValueRep: function(value, colId) {
+  }
+};
+
+// Exports from this module
+exports.DomDecorator = DomDecorator;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/dom-view.css
@@ -0,0 +1,111 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/******************************************************************************/
+/* General */
+
+body {
+  padding: 0;
+  margin: 0;
+  height: 100%;
+}
+
+/******************************************************************************/
+/* TreeView Customization */
+
+.treeTable {
+  width: 100%;
+}
+
+/* Space for read only properties icon */
+.treeTable td.treeValueCell {
+  padding-left: 16px;
+}
+
+/* Read only properties have a padlock icon */
+.treeTable tr:not(.writable) td.treeValueCell {
+  background: url("chrome://devtools/skin/images/firebug/read-only.svg") no-repeat;
+  background-position: 1px 5px;
+  background-size: 10px 10px;
+}
+
+/* Non-enumerable properties are grayed out */
+.treeTable tr:not(.enumerable) td.treeValueCell {
+  opacity: 0.7;
+}
+
+.treeTable > tbody > tr > td {
+  border-bottom: 1px solid #EFEFEF;
+}
+
+/* Label Types */
+.treeTable .userLabel,
+.treeTable .userClassLabel,
+.treeTable .userFunctionLabel {
+  font-weight: bold;
+}
+
+.treeTable .userLabel {
+  color: #000000;
+}
+
+.treeTable .userClassLabel {
+  color: #E90000;
+}
+
+.treeTable .userFunctionLabel {
+  color: #025E2A;
+}
+
+.treeTable .domLabel {
+  color: #000000;
+}
+
+.treeTable .domClassLabel {
+  color: #E90000;
+}
+
+.treeTable .domFunctionLabel {
+  color: #025E2A;
+}
+
+.treeTable .ordinalLabel {
+  color: SlateBlue;
+  font-weight: bold;
+}
+
+/******************************************************************************/
+/* Selection */
+
+.treeTable .treeRow:hover a,
+.treeTable .treeRow:hover span {
+  color: var(--theme-selection-color) !important;
+}
+
+/******************************************************************************/
+/* Toolbar */
+
+.toolbar {
+  position: fixed;
+  width: 100%;
+  top: 0;
+  z-index: 2;
+}
+
+.treeTable {
+  z-index: 1;
+  margin-top: 25px;
+}
+
+/******************************************************************************/
+/* Theme Dark */
+
+.theme-dark .treeTable > tbody > tr > td {
+  border-bottom: none;
+}
+
+.theme-dark body {
+  background-color: var(--theme-body-background);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/dom-view.js
@@ -0,0 +1,58 @@
+/* -*- 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";
+
+// React & Redux
+const React = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+const { combineReducers } = require("devtools/client/shared/vendor/redux");
+
+// DOM Panel
+const MainFrame = React.createFactory(require("./components/main-frame"));
+
+// Store
+const createStore = require("devtools/client/shared/redux/create-store")({
+  log: false
+});
+
+const { reducers } = require("./reducers/index");
+const store = createStore(combineReducers(reducers));
+
+/**
+ * This object represents view of the DOM panel and is responsible
+ * for rendering the content. It renders the top level ReactJS
+ * component: the MainFrame.
+ */
+function DomView() {
+  addEventListener("devtools/chrome/message",
+    this.onMessage.bind(this), true);
+}
+
+DomView.prototype = {
+  initialize: function(rootGrip) {
+    let content = document.querySelector("#content");
+    let mainFrame = MainFrame({
+      object: rootGrip,
+    });
+
+    // Render top level component
+    let provider = React.createElement(Provider, {store: store}, mainFrame);
+    this.mainFrame = ReactDOM.render(provider, content);
+  },
+
+  onMessage: function(event) {
+    let data = event.data;
+    let method = data.type;
+
+    if (typeof this[method] == "function") {
+      this[method](data.args);
+    }
+  },
+};
+
+// Construct DOM panel view object.
+new DomView();
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/grip-provider.js
@@ -0,0 +1,99 @@
+/* -*- 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 { fetchProperties } = require("./actions/grips");
+const { Property } = require("./reducers/grips");
+
+// Implementation
+function GripProvider(grips, dispatch) {
+  this.grips = grips;
+  this.dispatch = dispatch;
+}
+
+/**
+ * This object provides data for the tree displayed in the tooltip
+ * content.
+ */
+GripProvider.prototype = {
+  /**
+   * Fetches properties from the backend. These properties might be
+   * displayed as child objects in e.g. a tree UI widget.
+   */
+  getChildren: function(object) {
+    let grip = object;
+    if (object instanceof Property) {
+      grip = this.getValue(object);
+    }
+
+    if (!grip || !grip.actor) {
+      return [];
+    }
+
+    let props = this.grips.get(grip.actor);
+    if (!props) {
+      // Fetch missing data from the backend. Returning a promise
+      // from data provider causes the tree to show a spinner.
+      return this.dispatch(fetchProperties(grip));
+    }
+
+    return props;
+  },
+
+  hasChildren: function(object) {
+    if (object instanceof Property) {
+      let value = this.getValue(object);
+      if (!value) {
+        return false;
+      }
+
+      let hasChildren = value.ownPropertyLength > 0;
+
+      if (value.preview) {
+        hasChildren = hasChildren || value.preview.ownPropertiesLength > 0;
+      }
+
+      if (value.preview) {
+        let preview = value.preview;
+        let k = preview.kind;
+        let objectsWithProps = ["DOMNode", "ObjectWithURL"];
+        hasChildren = hasChildren || (objectsWithProps.indexOf(k) != -1);
+        hasChildren = hasChildren || (k == "ArrayLike" && preview.length > 0);
+      }
+
+      return (value.type == "object" && hasChildren);
+    }
+  },
+
+  getValue: function(object) {
+    if (object instanceof Property) {
+      let value = object.value;
+      return (typeof value.value != "undefined") ? value.value :
+        value.getterValue;
+    }
+
+    return object;
+  },
+
+  getLabel: function(object) {
+    if (object instanceof Property) {
+      return object.name;
+    }
+  },
+
+  getKey: function(object) {
+    if (object instanceof Property) {
+      return object.key;
+    }
+  },
+
+  getType: function(object) {
+    return object.class ? object.class : "";
+  },
+};
+
+// Exports from this module
+exports.GripProvider = GripProvider;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/moz.build
@@ -0,0 +1,19 @@
+# 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',
+]
+
+DevToolsModules(
+    'constants.js',
+    'dom-decorator.js',
+    'dom-view.css',
+    'dom-view.js',
+    'grip-provider.js',
+    'utils.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/filter.js
@@ -0,0 +1,29 @@
+/* -*- 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 constants = require("../constants");
+
+/**
+ * Initial state definition
+ */
+function getInitialState() {
+  return "";
+}
+
+/**
+ * Filter displayed object properties.
+ */
+function filter(state = getInitialState(), action) {
+  if (action.type == constants.SET_VISIBILITY_FILTER) {
+    return action.filter;
+  }
+
+  return state;
+}
+
+// Exports from this module
+exports.filter = filter;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/grips.js
@@ -0,0 +1,110 @@
+/* -*- 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 constants = require("../constants");
+
+/**
+ * Initial state definition
+ */
+function getInitialState() {
+  return new Map();
+}
+
+/**
+ * Maintain a cache of received grip responses from the backend.
+ */
+function grips(state = getInitialState(), action) {
+  // This reducer supports only one action, fetching actor properties
+  // from the backend so, bail out if we are dealing with any other
+  // action.
+  if (action.type != constants.FETCH_PROPERTIES) {
+    return state;
+  }
+
+  switch (action.status) {
+    case "start":
+      return onRequestProperties(state, action);
+    case "end":
+      return onReceiveProperties(state, action);
+  }
+
+  return state;
+}
+
+/**
+ * Handle requestProperties action
+ */
+function onRequestProperties(state, action) {
+  return state;
+}
+
+/**
+ * Handle receiveProperties action
+ */
+function onReceiveProperties(cache, action) {
+  let response = action.response;
+  let from = response.from;
+
+  // Properly deal with getters.
+  mergeProperties(response);
+
+  // Compute list of requested children.
+  let ownProps = response.ownProperties || response.preview.ownProperties || [];
+  let props = Object.keys(ownProps).map(key => {
+    return new Property(key, ownProps[key], key);
+  });
+
+  props.sort(sortName);
+
+  // Return new state/map.
+  let newCache = new Map(cache);
+  newCache.set(from, props);
+
+  return newCache;
+}
+
+// Helpers
+
+function mergeProperties(response) {
+  let { ownProperties } = response;
+
+  // 'safeGetterValues' is new and isn't necessary defined on old grips.
+  let safeGetterValues = response.safeGetterValues || {};
+
+  // Merge the safe getter values into one object such that we can use it
+  // in variablesView.
+  for (let name of Object.keys(safeGetterValues)) {
+    if (name in ownProperties) {
+      let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
+      ownProperties[name].getterValue = getterValue;
+      ownProperties[name].getterPrototypeLevel = getterPrototypeLevel;
+    } else {
+      ownProperties[name] = safeGetterValues[name];
+    }
+  }
+}
+
+function sortName(a, b) {
+  // Display non-enumerable properties at the end.
+  if (!a.value.enumerable && b.value.enumerable) {
+    return 1;
+  }
+  if (a.value.enumerable && !b.value.enumerable) {
+    return -1;
+  }
+  return a.name > b.name ? 1 : -1;
+}
+
+function Property(name, value, key) {
+  this.name = name;
+  this.value = value;
+  this.key = key;
+}
+
+// Exports from this module
+exports.grips = grips;
+exports.Property = Property;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/index.js
@@ -0,0 +1,14 @@
+/* -*- 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 { grips } = require("./grips");
+const { filter } = require("./filter");
+
+exports.reducers = {
+  grips,
+  filter,
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/moz.build
@@ -0,0 +1,10 @@
+# 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(
+    'filter.js',
+    'grips.js',
+    'index.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/utils.js
@@ -0,0 +1,27 @@
+/* -*- 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";
+
+/**
+ * The default localization just returns the last part of the key
+ * (all after the last dot).
+ */
+const DefaultL10N = {
+  getStr: function(key) {
+    let index = key.lastIndexOf(".");
+    return key.substr(index + 1);
+  }
+};
+
+/**
+ * The 'l10n' object is set by main.js in case the DOM panel content
+ * runs within a scope with chrome privileges.
+ *
+ * Note that DOM panel content can also run within a scope with no chrome
+ * privileges, e.g. in an iframe with type 'content' or in a browser tab,
+ * which allows using our own tools for development.
+ */
+exports.l10n = window.l10n || DefaultL10N;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/dom-panel.js
@@ -0,0 +1,188 @@
+/* -*- 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 { Cu } = require("chrome");
+const { defer } = require("sdk/core/promise");
+const { ObjectClient } = require("devtools/shared/client/main");
+
+const promise = require("promise");
+const EventEmitter = require("devtools/shared/event-emitter");
+
+/**
+ * This object represents DOM panel. It's responsibility is to
+ * render Document Object Model of the current debugger target.
+ */
+function DomPanel(iframeWindow, toolbox) {
+  this.panelWin = iframeWindow;
+  this._toolbox = toolbox;
+
+  this.onTabNavigated = this.onTabNavigated.bind(this);
+  this.onContentMessage = this.onContentMessage.bind(this);
+
+  EventEmitter.decorate(this);
+}
+
+DomPanel.prototype = {
+  /**
+   * Open is effectively an asynchronous constructor.
+   *
+   * @return object
+   *         A promise that is resolved when the DOM panel completes opening.
+   */
+  open: Task.async(function*() {
+    if (this._opening) {
+      return this._opening;
+    }
+
+    let deferred = promise.defer();
+    this._opening = deferred.promise;
+
+    // Local monitoring needs to make the target remote.
+    if (!this.target.isRemote) {
+      yield this.target.makeRemote();
+    }
+
+    this.initialize();
+
+    this.isReady = true;
+    this.emit("ready");
+
+    deferred.resolve(this);
+    return this._opening;
+  }),
+
+  // Initialization
+
+  initialize: function() {
+    this.panelWin.addEventListener("devtools/content/message",
+      this.onContentMessage, true);
+
+    this.target.on("navigate", this.onTabNavigated);
+
+    let provider = {
+      getPrototypeAndProperties: this.getPrototypeAndProperties.bind(this)
+    };
+
+    exportIntoContentScope(this.panelWin, provider, "DomProvider");
+
+    this.doRefresh();
+  },
+
+  destroy: Task.async(function*() {
+    if (this._destroying) {
+      return this._destroying;
+    }
+
+    let deferred = promise.defer();
+    this._destroying = deferred.promise;
+
+    this.target.off("navigate", this.onTabNavigated);
+
+    this.emit("destroyed");
+
+    deferred.resolve();
+    return this._destroying;
+  }),
+
+  // Events
+
+  doRefresh: function() {
+    this.refresh().then(rootGrip => {
+      this.postContentMessage("initialize", rootGrip);
+    });
+  },
+
+  onTabNavigated: function() {
+    this.doRefresh();
+  },
+
+  getPrototypeAndProperties: function(grip) {
+    let deferred = defer();
+
+    if (!grip.actor) {
+      console.error("No actor!", grip);
+      deferred.reject(new Error("Failed to get actor from grip."));
+      return deferred.promise;
+    }
+
+    if (!this.target) {
+      console.error("No target!", grip);
+      deferred.reject(new Error("Failed to get debugger target."));
+      return deferred.promise;
+    }
+
+    let client = new ObjectClient(this.target.client, grip);
+    client.getPrototypeAndProperties(deferred.resolve);
+
+    return deferred.promise;
+  },
+
+  // Refresh
+
+  refresh: function() {
+    let deferred = defer();
+
+    // Attach Console. It might involve RDP communication, so wait
+    // asynchronously for the result
+    this.target.activeConsole.evaluateJSAsync("window", res => {
+      deferred.resolve(res.result);
+    });
+
+    return deferred.promise;
+  },
+
+  // Helpers
+
+  postContentMessage: function(type, args) {
+    let data = {
+      type: type,
+      args: args,
+    };
+
+    let event = new this.panelWin.MessageEvent("devtools/chrome/message", {
+      bubbles: true,
+      cancelable: true,
+      data: data,
+    });
+
+    this.panelWin.dispatchEvent(event);
+  },
+
+  onContentMessage: function(event) {
+    let data = event.data;
+    let method = data.type;
+    if (typeof this[method] == "function") {
+      this[method](data.args);
+    }
+  },
+
+  get target() {
+    return this._toolbox.target;
+  },
+};
+
+// Helpers
+
+function exportIntoContentScope(win, obj, defineAs) {
+  let clone = Cu.createObjectIn(win, {
+    defineAs: defineAs
+  });
+
+  let props = Object.getOwnPropertyNames(obj);
+  for (let i = 0; i < props.length; i++) {
+    let propName = props[i];
+    let propValue = obj[propName];
+    if (typeof propValue == "function") {
+      Cu.exportFunction(propValue, clone, {
+        defineAs: propName
+      });
+    }
+  }
+}
+
+// Exports from this module
+exports.DomPanel = DomPanel;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/dom.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 href="resource://devtools/client/dom/content/dom-view.css" rel="stylesheet" />
+  <link href="resource://devtools/client/jsonview/css/toolbar.css" rel="stylesheet" />
+  <link href="resource://devtools/client/shared/components/tree/tree-view.css" rel="stylesheet" />
+  <link href="resource://devtools/client/dom/content/components/search-box.css" rel="stylesheet" />
+
+  <script type="text/javascript;version=1.8"
+          src="chrome://devtools/content/shared/theme-switching.js"></script>
+</head>
+<body class="theme-body devtools-monospace" role="application">
+  <div id="content"></div>
+  <script type="text/javascript" src="./main.js"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/main.js
@@ -0,0 +1,26 @@
+/* -*- 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 { utils: Cu } = Components;
+
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+
+// Module Loader
+const require = BrowserLoader({
+  baseURI: "resource://devtools/client/dom/",
+  window: this
+}).require;
+
+XPCOMUtils.defineConstant(this, "require", require);
+
+// Localization
+const { LocalizationHelper } = require("devtools/client/shared/l10n");
+this.l10n = new LocalizationHelper("chrome://devtools/locale/dom.properties");
+
+// Load DOM panel content
+require("./content/dom-view.js");
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/moz.build
@@ -0,0 +1,14 @@
+# 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 += [
+    'content',
+]
+
+DevToolsModules(
+    'dom-panel.js',
+    'dom.html',
+    'main.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/head.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -137,16 +137,19 @@ devtools.jar:
     content/eyedropper/eyedropper.xul (eyedropper/eyedropper.xul)
     content/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
     content/eyedropper/nocursor.css (eyedropper/nocursor.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/initializer.js (aboutdebugging/initializer.js)
     content/responsive.html/index.xhtml (responsive.html/index.xhtml)
     content/responsive.html/index.js (responsive.html/index.js)
+    content/dom/dom.html (dom/dom.html)
+    content/dom/content/dom-view.css (dom/content/dom-view.css)
+    content/dom/main.js (dom/main.js)
 %   skin devtools classic/1.0 %skin/
     skin/devtools-browser.css (themes/devtools-browser.css)
     skin/common.css (themes/common.css)
     skin/splitters.css (themes/splitters.css)
     skin/dark-theme.css (themes/dark-theme.css)
     skin/light-theme.css (themes/light-theme.css)
     skin/firebug-theme.css (themes/firebug-theme.css)
     skin/toolbars.css (themes/toolbars.css)
@@ -191,16 +194,17 @@ devtools.jar:
     skin/images/breadcrumbs-divider@2x.png (themes/images/breadcrumbs-divider@2x.png)
     skin/images/breadcrumbs-scrollbutton.png (themes/images/breadcrumbs-scrollbutton.png)
     skin/images/breadcrumbs-scrollbutton@2x.png (themes/images/breadcrumbs-scrollbutton@2x.png)
     skin/animationinspector.css (themes/animationinspector.css)
     skin/eyedropper.css (themes/eyedropper.css)
     skin/canvasdebugger.css (themes/canvasdebugger.css)
     skin/debugger.css (themes/debugger.css)
     skin/netmonitor.css (themes/netmonitor.css)
+    skin/dom.css (themes/dom.css)
     skin/performance.css (themes/performance.css)
     skin/memory.css (themes/memory.css)
     skin/promisedebugger.css (themes/promisedebugger.css)
     skin/images/timeline-filter.svg (themes/images/timeline-filter.svg)
     skin/scratchpad.css (themes/scratchpad.css)
     skin/shadereditor.css (themes/shadereditor.css)
     skin/storage.css (themes/storage.css)
     skin/splitview.css (themes/splitview.css)
@@ -274,16 +278,17 @@ devtools.jar:
     skin/images/emojis/emoji-tool-shadereditor.svg (themes/images/emojis/emoji-tool-shadereditor.svg)
     skin/images/emojis/emoji-tool-styleeditor.svg (themes/images/emojis/emoji-tool-styleeditor.svg)
     skin/images/emojis/emoji-tool-storage.svg (themes/images/emojis/emoji-tool-storage.svg)
     skin/images/emojis/emoji-tool-profiler.svg (themes/images/emojis/emoji-tool-profiler.svg)
     skin/images/emojis/emoji-tool-network.svg (themes/images/emojis/emoji-tool-network.svg)
     skin/images/emojis/emoji-tool-scratchpad.svg (themes/images/emojis/emoji-tool-scratchpad.svg)
     skin/images/emojis/emoji-tool-webaudio.svg (themes/images/emojis/emoji-tool-webaudio.svg)
     skin/images/emojis/emoji-tool-memory.svg (themes/images/emojis/emoji-tool-memory.svg)
+    skin/images/emojis/emoji-tool-dom.svg (themes/images/emojis/emoji-tool-dom.svg)
     skin/images/tool-options.svg (themes/images/tool-options.svg)
     skin/images/tool-webconsole.svg (themes/images/tool-webconsole.svg)
     skin/images/tool-canvas.svg (themes/images/tool-canvas.svg)
     skin/images/tool-debugger.svg (themes/images/tool-debugger.svg)
     skin/images/tool-debugger-paused.svg (themes/images/tool-debugger-paused.svg)
     skin/images/debugging-addons.svg (themes/images/debugging-addons.svg)
     skin/images/debugging-devices.svg (themes/images/debugging-devices.svg)
     skin/images/debugging-workers.svg (themes/images/debugging-workers.svg)
@@ -293,16 +298,17 @@ devtools.jar:
     skin/images/tool-storage.svg (themes/images/tool-storage.svg)
     skin/images/tool-profiler.svg (themes/images/tool-profiler.svg)
     skin/images/tool-profiler-active.svg (themes/images/tool-profiler-active.svg)
     skin/images/tool-network.svg (themes/images/tool-network.svg)
     skin/images/tool-scratchpad.svg (themes/images/tool-scratchpad.svg)
     skin/images/tool-webaudio.svg (themes/images/tool-webaudio.svg)
     skin/images/tool-memory.svg (themes/images/tool-memory.svg)
     skin/images/tool-memory-active.svg (themes/images/tool-memory-active.svg)
+    skin/images/tool-dom.svg (themes/images/tool-dom.svg)
     skin/images/close.svg (themes/images/close.svg)
     skin/images/clear.svg (themes/images/clear.svg)
     skin/images/vview-delete.png (themes/images/vview-delete.png)
     skin/images/vview-delete@2x.png (themes/images/vview-delete@2x.png)
     skin/images/vview-edit.png (themes/images/vview-edit.png)
     skin/images/vview-edit@2x.png (themes/images/vview-edit@2x.png)
     skin/images/vview-lock.png (themes/images/vview-lock.png)
     skin/images/vview-lock@2x.png (themes/images/vview-lock@2x.png)
new file mode 100644
--- /dev/null
+++ b/devtools/client/locales/en-US/dom.properties
@@ -0,0 +1,39 @@
+# 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/.
+
+# LOCALIZATION NOTE These strings are used inside the DOM panel
+# which is available from the Web Developer sub-menu -> 'DOM'.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+# LOCALIZATION NOTE (dom.label):
+# This string is displayed in the title of the tab when the DOM panel is
+# displayed inside the developer tools window and in the Developer Tools Menu.
+dom.label=DOM
+
+# LOCALIZATION NOTE (dom.panelLabel):
+# This is used as the label for the toolbox panel.
+dom.panelLabel=DOM Panel
+
+# LOCALIZATION NOTE (dom.commandkey, dom.accesskey)
+# Used for the menuitem in the tool menu
+dom.commandkey=W
+dom.accesskey=D
+
+# LOCALIZATION NOTE (dom.tooltip):
+# This string is displayed in the tooltip of the tab when the DOM is
+# displayed inside the developer tools window.
+# Keyboard shortcut for DOM panel will be shown inside the brackets.
+dom.tooltip=DOM (%S)
+
+# LOCALIZATION NOTE (dom.filterDOMPanel): A placeholder text used for
+# DOM panel search box.
+dom.filterDOMPanel=Filter DOM Panel
+
+# LOCALIZATION NOTE (dom.refresh): A label for Refresh button in
+# DOM panel toolbar
+dom.refresh=Refresh
\ No newline at end of file
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -7,16 +7,17 @@
 include('../templates.mozbuild')
 
 DIRS += [
     'aboutdebugging',
     'animationinspector',
     'canvasdebugger',
     'commandline',
     'debugger',
+    'dom',
     'eyedropper',
     'framework',
     'inspector',
     'jsonview',
     'locales',
     'memory',
     'netmonitor',
     'performance',
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/attribute.js
@@ -0,0 +1,70 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { StringRep } = require("./string");
+
+  // Shortcuts
+  const { span } = React.DOM;
+  const { rep: StringRepFactory } = createFactories(StringRep);
+
+  /**
+   * Renders DOM attribute
+   */
+  let Attribute = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Attr",
+
+    getTitle: function(grip) {
+      return grip.preview.nodeName;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      let value = grip.preview.value;
+
+      return (
+        ObjectLink({className: "Attr"},
+          span({},
+            span({className: "attrTitle"},
+              this.getTitle(grip)
+            ),
+            span({className: "attrEqual"},
+              "="
+            ),
+            StringRepFactory({object: value})
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (type == "Attr" && grip.preview);
+  }
+
+  exports.Attribute = {
+    rep: Attribute,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/date-time.js
@@ -0,0 +1,61 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Used to render JS built-in Date() object.
+   */
+  let DateTime = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Date",
+
+    getTitle: function(grip) {
+      return new Date(grip.preview.timestamp).toString();
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: "Date"},
+          span({className: "objectTitle"},
+            this.getTitle(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (type == "Date" && grip.preview);
+  }
+
+  // Exports from this module
+  exports.DateTime = {
+    rep: DateTime,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/document.js
@@ -0,0 +1,72 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { getFileName } = require("./url");
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders DOM document object.
+   */
+  let Document = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Document",
+
+    getLocation: function(grip) {
+      let location = grip.preview.location;
+      return location ? getFileName(location) : "";
+    },
+
+    getTitle: function(win, context) {
+      return "document";
+    },
+
+    getTooltip: function(doc) {
+      return doc.location.href;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectBox({className: "object"},
+          span({className: "objectPropValue"},
+            this.getLocation(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (object.preview && type == "HTMLDocument");
+  }
+
+  // Exports from this module
+  exports.Document = {
+    rep: Document,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/event.js
@@ -0,0 +1,69 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  /**
+   * Renders DOM event objects.
+   */
+  let Event = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "event",
+
+    summarizeEvent: function(grip) {
+      let info = [grip.preview.type, " "];
+
+      let eventFamily = grip.class;
+      let props = grip.preview.properties;
+
+      if (eventFamily == "MouseEvent") {
+        info.push("clientX=", props.clientX, ", clientY=", props.clientY);
+      } else if (eventFamily == "KeyboardEvent") {
+        info.push("charCode=", props.charCode, ", keyCode=", props.keyCode);
+      } else if (eventFamily == "MessageEvent") {
+        info.push("origin=", props.origin, ", data=", props.data);
+      }
+
+      return info.join("");
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: "event"},
+          this.summarizeEvent(grip)
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "DOMEvent");
+  }
+
+  // Exports from this module
+  exports.Event = {
+    rep: Event,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/function.js
@@ -0,0 +1,60 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { cropString } = require("./string");
+
+  /**
+   * This component represents a template for Function objects.
+   */
+  let Func = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Func",
+
+    summarizeFunction: function(grip) {
+      let name = grip.displayName || grip.name || "function";
+      return cropString(name + "()", 100);
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectLink({className: "function"},
+          this.summarizeFunction(grip)
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return (type == "function");
+    }
+
+    return (type == "Function");
+  }
+
+  // Exports from this module
+
+  exports.Func = {
+    rep: Func,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -0,0 +1,209 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // Dependencies
+  const React = require("devtools/client/shared/vendor/react");
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { Caption } = createFactories(require("./caption"));
+
+  // Shortcuts
+  const { a, span } = React.DOM;
+
+  /**
+   * Renders an array. The array is enclosed by left and right bracket
+   * and the max number of rendered items depends on the current mode.
+   */
+  let GripArray = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+      provider: React.PropTypes.object,
+    },
+
+    displayName: "GripArray",
+
+    getLength: function(grip) {
+      return grip.preview ? grip.preview.length : 0;
+    },
+
+    getTitle: function(object, context) {
+      return "[" + object.length + "]";
+    },
+
+    arrayIterator: function(grip, max) {
+      let items = [];
+
+      if (!grip.preview || !grip.preview.length) {
+        return items;
+      }
+
+      let array = grip.preview.items;
+      if (!array) {
+        return items;
+      }
+
+      let provider = this.props.provider;
+      if (!provider) {
+        return items;
+      }
+
+      let delim;
+
+      for (let i = 0; i < array.length && i <= max; i++) {
+        try {
+          let value = provider.getValue(array[i]);
+
+          delim = (i == array.length - 1 ? "" : ", ");
+
+          if (value === array) {
+            items.push(Reference({
+              key: i,
+              object: value,
+              delim: delim}
+            ));
+          } else {
+            items.push(GripArrayItem(Object.assign({}, this.props, {
+              key: i,
+              object: value,
+              delim: delim}
+            )));
+          }
+        } catch (exc) {
+          items.push(GripArrayItem(Object.assign({}, this.props, {
+            object: exc,
+            delim: delim,
+            key: i}
+          )));
+        }
+      }
+
+      if (array.length > max + 1) {
+        items.pop();
+        items.push(Caption({
+          key: "more",
+          object: "more..."}
+        ));
+      }
+
+      return items;
+    },
+
+    hasSpecialProperties: function(array) {
+      return false;
+    },
+
+    // Event Handlers
+
+    onToggleProperties: function(event) {
+    },
+
+    onClickBracket: function(event) {
+    },
+
+    render: function() {
+      let mode = this.props.mode || "short";
+      let object = this.props.object;
+
+      let items;
+
+      if (mode == "tiny") {
+        items = span({className: "length"}, this.getLength(object));
+      } else {
+        let max = (mode == "short") ? 3 : 300;
+        items = this.arrayIterator(object, max);
+      }
+
+      return (
+        ObjectBox({
+          className: "array",
+          onClick: this.onToggleProperties},
+          a({
+            className: "objectLink",
+            onclick: this.onClickBracket},
+            span({
+              className: "arrayLeftBracket",
+              role: "presentation"},
+              "["
+            )
+          ),
+          items,
+          a({
+            className: "objectLink",
+            onclick: this.onClickBracket},
+            span({
+              className: "arrayRightBracket",
+              role: "presentation"},
+              "]"
+            )
+          ),
+          span({
+            className: "arrayProperties",
+            role: "group"}
+          )
+        )
+      );
+    },
+  });
+
+  /**
+   * Renders array item. Individual values are separated by
+   * a delimiter (a comma by default).
+   */
+  let GripArrayItem = React.createFactory(React.createClass({
+    propTypes: {
+      delim: React.PropTypes.string,
+    },
+
+    displayName: "GripArrayItem",
+
+    render: function() {
+      let { Rep } = createFactories(require("./rep"));
+
+      return (
+        span({},
+          Rep(Object.assign({}, this.props, {
+            mode: "tiny"
+          })),
+          this.props.delim
+        )
+      );
+    }
+  }));
+
+  /**
+   * Renders cycle references in an array.
+   */
+  let Reference = React.createFactory(React.createClass({
+    displayName: "Reference",
+
+    render: function() {
+      return (
+        span({title: "Circular reference"},
+          "[...]"
+        )
+      );
+    }
+  }));
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "ArrayLike");
+  }
+
+  // Exports from this module
+  exports.GripArray = {
+    rep: GripArray,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/grip.js
@@ -0,0 +1,217 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Dependencies
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { Caption } = createFactories(require("./caption"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * @template TODO docs
+   */
+  const Grip = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+    },
+
+    displayName: "Grip",
+
+    getTitle: function() {
+      return "";
+    },
+
+    longPropIterator: function(object) {
+      try {
+        return this.propIterator(object, 100);
+      } catch (err) {
+        console.error(err);
+      }
+      return [];
+    },
+
+    shortPropIterator: function(object) {
+      try {
+        return this.propIterator(object, 3);
+      } catch (err) {
+        console.error(err);
+      }
+      return [];
+    },
+
+    propIterator: function(object, max) {
+      // Property filter. Show only interesting properties to the user.
+      let isInterestingProp = (type, value) => {
+        return (
+          type == "boolean" ||
+          type == "number" ||
+          type == "string" ||
+          type == "object"
+        );
+      };
+
+      // Object members with non-empty values are preferred since it gives the
+      // user a better overview of the object.
+      let props = this.getProps(object, max, isInterestingProp);
+
+      if (props.length <= max) {
+        // There are not enough props yet (or at least, not enough props to
+        // be able to know whether we should print "more..." or not).
+        // Let's display also empty members and functions.
+        props = props.concat(this.getProps(object, max, (t, value) => {
+          return !isInterestingProp(t, value);
+        }));
+      }
+
+      // getProps() can return max+1 properties (it can't return more)
+      // to indicate that there is more props than allowed. Remove the last
+      // one and append 'more...' postfix in such case.
+      if (props.length > max) {
+        props.pop();
+        props.push(Caption({
+          key: "more",
+          object: "more...",
+        }));
+      } else if (props.length > 0) {
+        // Remove the last comma.
+        // NOTE: do not change comp._store.props directly to update a property,
+        // it should be re-rendered or cloned with changed props
+        let last = props.length - 1;
+        props[last] = React.cloneElement(props[last], {
+          delim: ""
+        });
+      }
+
+      return props;
+    },
+
+    getProps: function(object, max, filter) {
+      let props = [];
+
+      max = max || 3;
+      if (!object) {
+        return props;
+      }
+
+      try {
+        let ownProperties = object.preview ? object.preview.ownProperties : [];
+        for (let name in ownProperties) {
+          if (props.length > max) {
+            return props;
+          }
+
+          let prop = ownProperties[name];
+          let value = prop.value || {};
+
+          // Type is specified in grip's "class" field and for primitive
+          // values use typeof.
+          let type = (value.class || typeof value);
+          type = type.toLowerCase();
+
+          // Show only interesting properties.
+          if (filter(type, value)) {
+            props.push(PropRep(Object.assign({}, this.props, {
+              key: name,
+              mode: "tiny",
+              name: name,
+              object: value,
+              equal: ": ",
+              delim: ", ",
+            })));
+          }
+        }
+      } catch (err) {
+        console.error(err);
+      }
+
+      return props;
+    },
+
+    render: function() {
+      let object = this.props.object;
+      let props = this.shortPropIterator(object);
+
+      if (this.props.mode == "tiny" || !props.length) {
+        return (
+          ObjectBox({className: "object"},
+            span({className: "objectTitle"}, this.getTitle(object)),
+            span({className: "objectLeftBrace", role: "presentation"}, "{}")
+          )
+        );
+      }
+
+      return (
+        ObjectBox({className: "object"},
+          span({className: "objectTitle"}, this.getTitle(object)),
+          span({className: "objectLeftBrace", role: "presentation"}, "{"),
+          props,
+          span({className: "objectRightBrace"}, "}")
+        )
+      );
+    },
+  });
+
+  /**
+   * Property for a grip object.
+   */
+  let PropRep = React.createFactory(React.createClass({
+    propTypes: {
+      name: React.PropTypes.string,
+      equal: React.PropTypes.string,
+      delim: React.PropTypes.string,
+    },
+
+    displayName: "PropRep",
+
+    render: function() {
+      let { Rep } = createFactories(require("./rep"));
+
+      return (
+        span({},
+          span({
+            "className": "nodeName"},
+            this.props.name),
+          span({
+            "className": "objectEqual",
+            role: "presentation"},
+            this.props.equal
+          ),
+          Rep(this.props),
+          span({
+            "className": "objectComma",
+            role: "presentation"},
+            this.props.delim
+          )
+        )
+      );
+    }
+  }));
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (object.preview && object.preview.ownProperties);
+  }
+
+  // Exports from this module
+  exports.Grip = {
+    rep: Grip,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -1,21 +1,35 @@
 # -*- Mode: python; c-basic-offset: 4; 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(
     'array.js',
+    'attribute.js',
     'caption.js',
+    'date-time.js',
+    'document.js',
+    'event.js',
+    'function.js',
+    'grip-array.js',
+    'grip.js',
+    'named-node-map.js',
     'null.js',
     'number.js',
     'object-box.js',
     'object-link.js',
+    'object-with-text.js',
+    'object-with-url.js',
     'object.js',
+    'regexp.js',
     'rep-utils.js',
     'rep.js',
     'reps.css',
     'string.js',
+    'stylesheet.js',
+    'text-node.js',
     'undefined.js',
     'url.js',
+    'window.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/named-node-map.js
@@ -0,0 +1,172 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { Caption } = createFactories(require("./caption"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Used to render a map of values provided as a grip.
+   */
+  let NamedNodeMap = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+      provider: React.PropTypes.object,
+    },
+
+    className: "NamedNodeMap",
+
+    getLength: function(object) {
+      return object.preview.length;
+    },
+
+    getTitle: function(object) {
+      return object.class ? object.class : "";
+    },
+
+    getItems: function(array, max) {
+      let items = this.propIterator(array, max);
+
+      items = items.map(item => PropRep(item));
+
+      if (items.length > max + 1) {
+        items.pop();
+        items.push(Caption({
+          key: "more",
+          object: "more...",
+        }));
+      }
+
+      return items;
+    },
+
+    propIterator: function(grip, max) {
+      max = max || 3;
+
+      let props = [];
+
+      let provider = this.props.provider;
+      if (!provider) {
+        return props;
+      }
+
+      let ownProperties = grip.preview ? grip.preview.ownProperties : [];
+      for (let name in ownProperties) {
+        if (props.length > max) {
+          break;
+        }
+
+        let item = ownProperties[name];
+        let label = provider.getLabel(item);
+        let value = provider.getValue(item);
+
+        props.push(Object.assign({}, this.props, {
+          name: label,
+          object: value,
+          equal: ": ",
+          delim: ", ",
+        }));
+      }
+
+      return props;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      let mode = this.props.mode;
+
+      let items;
+      if (mode == "tiny") {
+        items = this.getLength(grip);
+      } else {
+        let max = (mode == "short") ? 3 : 100;
+        items = this.getItems(grip, max);
+      }
+
+      return (
+        ObjectLink({className: "NamedNodeMap"},
+          span({className: "objectTitle"},
+            this.getTitle(grip)
+          ),
+          span({
+            className: "arrayLeftBracket",
+            role: "presentation"},
+            "["
+          ),
+          items,
+          span({
+            className: "arrayRightBracket",
+            role: "presentation"},
+            "]"
+          )
+        )
+      );
+    },
+  });
+
+  /**
+   * Property for a grip object.
+   */
+  let PropRep = React.createFactory(React.createClass({
+    propTypes: {
+      equal: React.PropTypes.string,
+      delim: React.PropTypes.string,
+    },
+
+    displayName: "PropRep",
+
+    render: function() {
+      const { Rep } = createFactories(require("./rep"));
+
+      return (
+        span({},
+          span({
+            className: "nodeName"},
+            "$prop.name"
+          ),
+          span({
+            className: "objectEqual",
+            role: "presentation"},
+            this.props.equal
+          ),
+          Rep(this.props),
+          span({
+            className: "objectComma",
+            role: "presentation"},
+            this.props.delim
+          )
+        )
+      );
+    }
+  }));
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (type == "NamedNodeMap" && grip.preview);
+  }
+
+  // Exports from this module
+  exports.NamedNodeMap = {
+    rep: NamedNodeMap,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/object-with-text.js
@@ -0,0 +1,65 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a grip object with textual data.
+   */
+  let ObjectWithText = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "ObjectWithText",
+
+    getType: function(grip) {
+      return grip.class;
+    },
+
+    getDescription: function(grip) {
+      return (grip.preview.kind == "ObjectWithText") ? grip.preview.text : "";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: this.getType(grip)},
+          span({className: "objectPropValue"},
+            this.getDescription(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "ObjectWithText");
+  }
+
+  // Exports from this module
+  exports.ObjectWithText = {
+    rep: ObjectWithText,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/object-with-url.js
@@ -0,0 +1,65 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a grip object with URL data.
+   */
+  let ObjectWithURL = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "ObjectWithURL",
+
+    getType: function(grip) {
+      return grip.class;
+    },
+
+    getDescription: function(grip) {
+      return (grip.preview.kind == "ObjectWithURL") ? grip.preview.url : "";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: this.getType(grip)},
+          span({className: "objectPropValue"},
+            this.getDescription(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "ObjectWithURL");
+  }
+
+  // Exports from this module
+  exports.ObjectWithURL = {
+    rep: ObjectWithURL,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -10,38 +10,29 @@
 define(function(require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
   const { createFactories } = require("./rep-utils");
   const { ObjectBox } = createFactories(require("./object-box"));
   const { Caption } = createFactories(require("./caption"));
 
   // Shortcuts
-  const DOM = React.DOM;
+  const { span } = React.DOM;
 
   /**
    * Renders an object. An object is represented by a list of its
    * properties enclosed in curly brackets.
    */
   const Obj = React.createClass({
-    displayName: "Obj",
-
-    render: function() {
-      let object = this.props.object;
-      let props = this.shortPropIterator(object);
+    propTypes: {
+      object: React.PropTypes.object,
+      mode: React.PropTypes.string,
+    },
 
-      return (
-        ObjectBox({className: "object"},
-          DOM.span({className: "objectTitle"}, this.getTitle(object)),
-          DOM.span({className: "objectLeftBrace", role: "presentation"}, "{"),
-          props,
-          DOM.span({className: "objectRightBrace"}, "}")
-        )
-      );
-    },
+    displayName: "Obj",
 
     getTitle: function() {
       return "";
     },
 
     longPropIterator: function(object) {
       try {
         return this.propIterator(object, 100);
@@ -56,38 +47,37 @@ define(function(require, exports, module
         return this.propIterator(object, 3);
       } catch (err) {
         console.error(err);
       }
       return [];
     },
 
     propIterator: function(object, max) {
-      function isInterestingProp(t, value) {
-        return (t == "boolean" || t == "number" || (t == "string" && value) ||
-          (t == "object" && value && value.toString));
-      }
+      let isInterestingProp = (t, value) => {
+        // Do not pick objects, it could cause recursion.
+        return (t == "boolean" || t == "number" || (t == "string" && value));
+      };
 
       // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
       if (Object.prototype.toString.call(object) === "[object Generator]") {
         object = Object.getPrototypeOf(object);
       }
 
       // Object members with non-empty values are preferred since it gives the
       // user a better overview of the object.
-      let props = [];
-      this.getProps(props, object, max, isInterestingProp);
+      let props = this.getProps(object, max, isInterestingProp);
 
       if (props.length <= max) {
         // There are not enough props yet (or at least, not enough props to
         // be able to know whether we should print "more..." or not).
         // Let's display also empty members and functions.
-        this.getProps(props, object, max, function(t, value) {
+        props = props.concat(this.getProps(object, max, (t, value) => {
           return !isInterestingProp(t, value);
-        });
+        }));
       }
 
       if (props.length > max) {
         props.pop();
         props.push(Caption({
           key: "more",
           object: "more...",
         }));
@@ -95,28 +85,30 @@ define(function(require, exports, module
         // Remove the last comma.
         props[props.length - 1] = React.cloneElement(
           props[props.length - 1], { delim: "" });
       }
 
       return props;
     },
 
-    getProps: function(props, object, max, filter) {
+    getProps: function(object, max, filter) {
+      let props = [];
+
       max = max || 3;
       if (!object) {
-        return [];
+        return props;
       }
 
       let mode = this.props.mode;
 
       try {
         for (let name in object) {
           if (props.length > max) {
-            return [];
+            return props;
           }
 
           let value;
           try {
             value = object[name];
           } catch (exc) {
             continue;
           }
@@ -132,47 +124,69 @@ define(function(require, exports, module
               delim: ", ",
             }));
           }
         }
       } catch (err) {
         console.error(err);
       }
 
-      return [];
+      return props;
+    },
+
+    render: function() {
+      let object = this.props.object;
+      let props = this.shortPropIterator(object);
+
+      return (
+        ObjectBox({className: "object"},
+          span({className: "objectTitle"}, this.getTitle(object)),
+          span({className: "objectLeftBrace", role: "presentation"}, "{"),
+          props,
+          span({className: "objectRightBrace"}, "}")
+        )
+      );
     },
   });
 
   /**
    * Renders object property, name-value pair.
    */
   let PropRep = React.createFactory(React.createClass({
+    propTypes: {
+      object: React.PropTypes.any,
+      mode: React.PropTypes.string,
+      name: React.PropTypes.string,
+      equal: React.PropTypes.string,
+      delim: React.PropTypes.string,
+    },
+
     displayName: "PropRep",
 
     render: function() {
       let { Rep } = createFactories(require("./rep"));
       let object = this.props.object;
       let mode = this.props.mode;
 
       return (
-        DOM.span({},
-          DOM.span({
+        span({},
+          span({
             "className": "nodeName"},
             this.props.name
           ),
-          DOM.span({
+          span({
             "className": "objectEqual",
             role: "presentation"},
             this.props.equal
           ),
           Rep({
             object: object,
             mode: mode
           }),
-          DOM.span({
+          span({
             "className": "objectComma",
             role: "presentation"},
             this.props.delim
           )
         )
       );
     }
   }));
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/regexp.js
@@ -0,0 +1,69 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a grip object with regular expression.
+   */
+  let RegExp = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "regexp",
+
+    getTitle: function(grip) {
+      return grip.class;
+    },
+
+    getSource: function(grip) {
+      return grip.displayString;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: "regexp"},
+          span({className: "objectTitle"},
+            this.getTitle(grip)
+          ),
+          span(" "),
+          span({className: "regexpSource"},
+            this.getSource(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (type == "RegExp");
+  }
+
+  // Exports from this module
+  exports.RegExp = {
+    rep: RegExp,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/rep-utils.js
+++ b/devtools/client/shared/components/reps/rep-utils.js
@@ -19,11 +19,19 @@ define(function(require, exports, module
   function createFactories(args) {
     let result = {};
     for (let p in args) {
       result[p] = React.createFactory(args[p]);
     }
     return result;
   }
 
+  /**
+   * Returns true if the given object is a grip (see RDP protocol)
+   */
+  function isGrip(object) {
+    return object && object.actor;
+  }
+
   // Exports from this module
   exports.createFactories = createFactories;
+  exports.isGrip = isGrip;
 });
--- a/devtools/client/shared/components/reps/rep.js
+++ b/devtools/client/shared/components/reps/rep.js
@@ -6,56 +6,101 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function(require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { isGrip } = require("./rep-utils");
+
   // Load all existing rep templates
   const { Undefined } = require("./undefined");
   const { Null } = require("./null");
   const { StringRep } = require("./string");
   const { Number } = require("./number");
   const { ArrayRep } = require("./array");
   const { Obj } = require("./object");
 
+  // DOM types (grips)
+  const { Attribute } = require("./attribute");
+  const { DateTime } = require("./date-time");
+  const { Document } = require("./document");
+  const { Event } = require("./event");
+  const { Func } = require("./function");
+  const { NamedNodeMap } = require("./named-node-map");
+  const { RegExp } = require("./regexp");
+  const { StyleSheet } = require("./stylesheet");
+  const { TextNode } = require("./text-node");
+  const { Window } = require("./window");
+  const { ObjectWithText } = require("./object-with-text");
+  const { ObjectWithURL } = require("./object-with-url");
+  const { GripArray } = require("./grip-array");
+  const { Grip } = require("./grip");
+
   // List of all registered template.
   // XXX there should be a way for extensions to register a new
   // or modify an existing rep.
-  let reps = [Undefined, Null, StringRep, Number, ArrayRep, Obj];
-  let defaultRep;
+  let reps = [
+    RegExp,
+    StyleSheet,
+    Event,
+    DateTime,
+    TextNode,
+    NamedNodeMap,
+    Attribute,
+    Func,
+    ArrayRep,
+    Document,
+    Window,
+    ObjectWithText,
+    ObjectWithURL,
+    GripArray,
+    Grip,
+    Undefined,
+    Null,
+    StringRep,
+    Number,
+  ];
 
   /**
    * Generic rep that is using for rendering native JS types or an object.
    * The right template used for rendering is picked automatically according
    * to the current value type. The value must be passed is as 'object'
    * property.
    */
   const Rep = React.createClass({
+    propTypes: {
+      object: React.PropTypes.any,
+      defaultRep: React.PropTypes.object,
+    },
+
     displayName: "Rep",
 
     render: function() {
-      let rep = getRep(this.props.object);
+      let rep = getRep(this.props.object, this.props.defaultRep);
       return rep(this.props);
     },
   });
 
   // Helpers
 
   /**
    * Return a rep object that is responsible for rendering given
    * object.
    *
    * @param object {Object} Object to be rendered in the UI. This
    * can be generic JS object as well as a grip (handle to a remote
    * debuggee object).
+   *
+   * @param defaultObject {React.Component} The default template
+   * that should be used to render given object if none is found.
    */
-  function getRep(object) {
+  function getRep(object, defaultRep = Obj) {
     let type = typeof object;
     if (type == "object" && object instanceof String) {
       type = "string";
     }
 
     if (isGrip(object)) {
       type = object.class;
     }
@@ -65,22 +110,18 @@ define(function(require, exports, module
       try {
         // supportsObject could return weight (not only true/false
         // but a number), which would allow to priorities templates and
         // support better extensibility.
         if (rep.supportsObject(object, type)) {
           return React.createFactory(rep.rep);
         }
       } catch (err) {
-        console.error("reps.getRep; EXCEPTION ", err, err);
+        console.error(err);
       }
     }
 
     return React.createFactory(defaultRep.rep);
   }
 
-  function isGrip(object) {
-    return object && object.actor;
-  }
-
   // Exports from this module
   exports.Rep = Rep;
 });
--- a/devtools/client/shared/components/reps/string.js
+++ b/devtools/client/shared/components/reps/string.js
@@ -91,11 +91,14 @@ define(function(require, exports, module
     return (type == "string");
   }
 
   // Exports from this module
 
   exports.StringRep = {
     rep: StringRep,
     supportsObject: supportsObject,
-    isCropped: isCropped
   };
+
+  exports.isCropped = isCropped;
+  exports.cropString = cropString;
+  exports.cropMultipleLines = cropMultipleLines;
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/stylesheet.js
@@ -0,0 +1,67 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { getFileName } = require("./url");
+
+  // Shortcuts
+  const DOM = React.DOM;
+
+  /**
+   * Renders a grip representing CSSStyleSheet
+   */
+  let StyleSheet = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "object",
+
+    getLocation: function(grip) {
+      // Embedded stylesheets don't have URL and so, no preview.
+      let url = grip.preview ? grip.preview.url : "";
+      return url ? getFileName(url) : "";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectBox({className: "object"},
+          "StyleSheet ",
+          DOM.span({className: "objectPropValue"},
+            this.getLocation(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (type == "CSSStyleSheet");
+  }
+
+  // Exports from this module
+
+  exports.StyleSheet = {
+    rep: StyleSheet,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/text-node.js
@@ -0,0 +1,82 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { cropMultipleLines } = require("./string");
+
+  // Shortcuts
+  const DOM = React.DOM;
+
+  /**
+   * Renders DOM #text node.
+   */
+  let TextNode = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+    },
+
+    displayName: "TextNode",
+
+    getTextContent: function(grip) {
+      return cropMultipleLines(grip.preview.textContent);
+    },
+
+    getTitle: function(win, context) {
+      return "textNode";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      let mode = this.props.mode || "short";
+
+      if (mode == "short" || mode == "tiny") {
+        return (
+          ObjectLink({className: "textNode"},
+            "\"" + this.getTextContent(grip) + "\""
+          )
+        );
+      }
+
+      return (
+        ObjectLink({className: "textNode"},
+          "<",
+          DOM.span({className: "nodeTag"}, "TextNode"),
+          " textContent=\"",
+          DOM.span({className: "nodeValue"},
+            this.getTextContent(grip)
+          ),
+          "\"",
+          ">;"
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.class == "Text");
+  }
+
+  // Exports from this module
+  exports.TextNode = {
+    rep: TextNode,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/url.js
+++ b/devtools/client/shared/components/reps/url.js
@@ -27,12 +27,55 @@ define(function(require, exports, module
     return entries.map(entry => {
       return {
         name: entry[0],
         value: entry[1]
       };
     });
   }
 
+  function getFileName(url) {
+    let split = splitURLBase(url);
+    return split.name;
+  }
+
+  function splitURLBase(url) {
+    if (!isDataURL(url)) {
+      return splitURLTrue(url);
+    }
+    return {};
+  }
+
+  function isDataURL(url) {
+    return (url && url.substr(0, 5) == "data:");
+  }
+
+  function splitURLTrue(url) {
+    const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
+    let m = reSplitFile.exec(url);
+
+    if (!m) {
+      return {
+        name: url,
+        path: url
+      };
+    } else if (m[4] == "" && m[5] == "") {
+      return {
+        protocol: m[1],
+        domain: m[2],
+        path: m[3],
+        name: m[3] != "/" ? m[3] : m[2]
+      };
+    }
+
+    return {
+      protocol: m[1],
+      domain: m[2],
+      path: m[2] + m[3],
+      name: m[4] + m[5]
+    };
+  }
+
   // Exports from this module
   exports.parseURLParams = parseURLParams;
   exports.parseURLEncodedText = parseURLEncodedText;
+  exports.getFileName = getFileName;
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/window.js
@@ -0,0 +1,71 @@
+/* -*- 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";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { cropString } = require("./string");
+
+  // Shortcuts
+  const DOM = React.DOM;
+
+  /**
+   * Renders a grip representing a window.
+   */
+  let Window = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "Window",
+
+    getLocation: function(grip) {
+      return cropString(grip.preview.url);
+    },
+
+    getTitle: function(grip, context) {
+      return grip.class;
+    },
+
+    getTooltip: function(grip) {
+      return grip.preview.url;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectBox({className: "Window"},
+          DOM.span({className: "objectPropValue"},
+            this.getLocation(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (object.preview && type == "Window");
+  }
+
+  // Exports from this module
+  exports.Window = {
+    rep: Window,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/dom.css
@@ -0,0 +1,9 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+:root.theme-dark {
+}
+:root.theme-light {
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/emojis/emoji-tool-dom.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+  <path fill="#51BA7B" d="M487.819 258.669H439.1c-10.041 0-18.181-8.14-18.181-18.181s8.14-18.181 18.181-18.181h48.719c10.041 0 18.181 8.14 18.181 18.181s-8.14 18.181-18.181 18.181z"/>
+  <path fill="#BADEBE" d="M415.747 69.674s-.387.603-1.059 1.667a8.05 8.05 0 0 1-.638.799 59.208 59.208 0 0 0-1.057 1.933c-.357.806-.812 1.618-1.199 2.599a55.875 55.875 0 0 0-1.233 3.083c-.433 1.086-.783 2.248-1.19 3.426-.364 1.183-.742 2.395-1.064 3.614-.316 1.219-.645 2.445-.884 3.643-.286 1.219-.475 2.381-.679 3.523-.188 1.142-.308 2.214-.434 3.243-.041.505-.077.988-.118 1.457-.043.483-.07.939-.063 1.338-.007.812-.063 1.646 0 2.221.014.315.034.609.048.882 0 .14.007.273.015.399.014.105.027.209.041.301.113.777.161 1.26.161 1.26l.19 2.025a7.085 7.085 0 0 1-6.396 7.712c-2.829.267-5.421-1.177-6.774-3.467 0 0-.489-.826-1.308-2.353-.099-.189-.204-.392-.316-.603-.084-.203-.181-.413-.274-.63-.195-.448-.398-.932-.616-1.45-.484-1.058-.806-2.164-1.233-3.432a23.41 23.41 0 0 1-.561-1.933c-.174-.665-.349-1.352-.532-2.066-.301-1.387-.622-2.879-.876-4.393-.231-1.506-.491-3.089-.638-4.659-.195-1.583-.308-3.18-.419-4.784-.106-1.604-.147-3.201-.19-4.791 0-1.576-.027-3.145.043-4.665.034-1.513.125-2.998.238-4.42.077-1.408.28-2.802.414-4.063.188-1.31.371-2.48.588-3.622.301-1.31.622-2.529.853-3.411.31-1.212.477-1.913.477-1.913 1.968-7.887 9.967-12.692 17.856-10.724 7.894 1.975 12.691 9.968 10.725 17.862a14.708 14.708 0 0 1-1.815 4.266l-.083.126zm-56.35 17.01a14.892 14.892 0 0 0 1.036-4.518c.559-8.111-5.562-15.145-13.681-15.705-8.118-.56-15.151 5.562-15.711 13.681 0 0-.05.715-.133 1.976a70.1 70.1 0 0 0-.258 4.133c.014.665.055 1.233.091 1.912.048.638.063 1.366.154 2.025.174 1.331.308 2.823.595 4.245l.407 2.221c.168.729.335 1.478.511 2.234.322 1.527.783 3.033 1.19 4.575.477 1.526.91 3.068 1.457 4.574a88.52 88.52 0 0 0 1.654 4.483c.552 1.464 1.211 2.893 1.806 4.259a121.46 121.46 0 0 0 1.905 3.936c.694 1.254 1.255 2.41 1.948 3.496.665 1.086 1.226 2.052 1.864 2.936.622.882 1.079 1.611 1.618 2.248l1.59 1.941c1.849 2.255 4.973 3.243 7.887 2.234 3.74-1.289 5.715-5.373 4.426-9.107l-.469-1.338s-.168-.497-.469-1.366c-.162-.386-.303-1.051-.498-1.688-.204-.631-.357-1.478-.554-2.347-.217-.833-.344-1.891-.532-2.906a93.503 93.503 0 0 1-.428-3.362c-.097-1.198-.231-2.396-.274-3.664a61.991 61.991 0 0 1-.118-3.797c-.029-1.268.041-2.543.063-3.775.091-1.219.111-2.438.251-3.566.063-.56.12-1.121.174-1.661.077-.518.162-1.03.233-1.526.132-1.016.371-1.829.525-2.627.077-.407.21-.693.301-1.016.097-.294.181-.645.267-.841.188-.127.258-.147.342-.294l.751-1.829.079-.176z"/>
+  <path fill="#8ACCA0" d="M456.112 143.24c-11.449-34.634-48.8-53.434-83.441-41.983-34.634 11.449-53.431 48.807-41.982 83.442 17.717 53.598 16.873 94.849-2.59 126.04-1.716 2.393-3.661 5.076-4.907 6.585a66.228 66.228 0 0 0-7.289 6.104 66.535 66.535 0 0 0-3.758 2.035l-2.851 1.674c-.113.077-.154.112-.246.189-.041.035-.077.07-.125.112-.022.021-.029.035-.07.07l-.233.127c-.301.182-.629.371-.973.575-.364.203-.749.413-1.156.637a56.07 56.07 0 0 1-6.494 2.956c-2.711 1.044-6.03 2.109-10.03 3.04a104.442 104.442 0 0 1-13.996 2.179c-5.317.483-11.27.672-17.694.476a202.645 202.645 0 0 1-4.912-.224c-1.597-.105-3.208-.21-4.826-.322-4.134-.393-8.308-.784-12.517-1.191-1.907-.168-3.875-.42-5.821-.623-1.962-.217-3.776-.469-5.681-.693-1.828-.259-3.656-.504-5.437-.777-1.765-.287-3.537-.553-5.288-.882-1.738-.294-3.538-.673-5.331-1.023l-2.774-.603c-.925-.203-1.864-.406-2.865-.652l-2.943-.693-3.095-.77c-2.136-.539-4.26-1.078-6.382-1.618-8.713-2.241-17.861-4.617-26.604-6.732l-1.64-.398-.202-.05-.099-.028c-1.233-.357-.398-.104-.699-.189l-.4-.07-.792-.147a365.722 365.722 0 0 1-3.152-.56c-2.634-.476-5.24-.953-7.824-1.415a44.945 44.945 0 0 0-1.955-.322c-.631-.097-1.26-.196-1.891-.287-1.254-.189-2.5-.371-3.74-.56-2.516-.316-4.966-.658-7.439-.924-9.863-1.1-19.447-1.646-28.545-1.619-9.107.05-17.682.61-25.54 1.661-7.839 1.023-14.949 2.522-21.051 4.175a137.419 137.419 0 0 0-8.419 2.578 129.198 129.198 0 0 0-6.851 2.592c-2.032.847-3.79 1.646-5.204 2.326l-2.039 1.001c-.982.505-1.479.757-1.479.757-26.17 13.548-36.403 45.75-22.857 71.92 13.555 26.178 45.756 36.405 71.927 22.857l-1.45.735c-.301.14-.742.351-1.324.63-.344.155-.848.386-1.507.693-.251.133-.342.203-.21.21.169.015.504-.014 1.072-.063 1.163-.091 3.138-.259 5.955-.231 2.774 0 6.41.231 10.661.757 4.272.54 9.225 1.457 14.668 2.767 1.358.321 2.767.721 4.175 1.071.715.203 1.437.413 2.165.617l1.086.308c.378.105.735.203 1.023.302 1.394.441 2.808.882 4.231 1.331 1.765.575 3.544 1.149 5.337 1.731 7.824 2.472 15.711 5.092 24.357 7.978 2.227.743 4.462 1.478 6.709 2.221l3.496 1.142 3.692 1.17 1.864.589 1.946.588 3.923 1.177c2.704.77 5.387 1.555 8.175 2.269 2.76.75 5.527 1.415 8.294 2.087 2.745.658 5.464 1.248 8.168 1.843 2.634.54 5.344 1.121 7.901 1.604 2.584.476 5.107.981 7.704 1.429 2.543.441 5.072.876 7.586 1.31 2.208.364 4.407.722 6.6 1.086 1.443.217 2.885.427 4.315.644 1.366.182 2.724.371 4.077.553 2.711.351 5.428.666 8.139.946 10.851 1.128 21.682 1.647 32.23 1.513a239.007 239.007 0 0 0 30.479-2.291c9.666-1.366 18.689-3.313 26.716-5.548a191.408 191.408 0 0 0 20.791-7.061c1.428-.595 2.781-1.162 4.055-1.695 1.269-.568 2.459-1.093 3.566-1.59l.812-.364.912-.434c.602-.295 1.177-.568 1.731-.834 1.086-.54 2.073-1.023 2.955-1.464 1.919-.981 2.943-1.5 2.943-1.5l1.975-1.008a66.247 66.247 0 0 0 15.966-11.503 66.576 66.576 0 0 0 6.185-3.588c18.693-12.238 30.142-28.256 38.55-40.018l.926-1.294.862-1.338c23.738-36.839 36.169-79.029 36.947-125.397.602-35.782-5.867-74.417-19.227-114.833z"/>
+  <path fill="#FFF" d="M379.069 155.928l4.301 16.062h-.021c.007.028.027.028.034.042 1.688 6.311-2.059 12.798-8.363 14.486-6.312 1.688-12.799-2.059-14.487-8.364-.007-.014-.007-.021-.014-.049l-.05.014-4.301-16.062.14-.035c-1.057-5.989 2.55-11.887 8.532-13.492 5.969-1.597 12.048 1.709 14.116 7.425l.113-.027zm48.159-20.587c-2.697-5.45-9.107-8.048-14.858-5.792-5.765 2.263-8.693 8.532-6.971 14.367l-.132.049 6.08 15.481.05-.021c.007.021.007.027.014.042 2.387 6.08 9.254 9.071 15.334 6.683 6.08-2.381 9.071-9.254 6.682-15.334 0-.008-.027-.008-.034-.028l.021-.015-6.08-15.474-.106.042z"/>
+  <path fill="#51BA7B" d="M386.722 311.106l40.557 17.233c9.247 3.93 13.555 14.612 9.632 23.859-3.93 9.253-14.619 13.561-23.866 9.632a21.165 21.165 0 0 1-1.24-.581l-39.143-20.223c-8.118-4.196-11.292-14.171-7.104-22.29 3.994-7.728 13.284-10.95 21.164-7.63"/>
+  <path fill="#74C48D" d="M394.574 405.513c-12.623 0-25.31-3.56-36.185-10.523a5.688 5.688 0 0 1 6.134-9.581c15.343 9.826 35.001 11.501 51.304 4.37a5.69 5.69 0 0 1 4.558 10.424c-8.137 3.557-16.959 5.31-25.811 5.31zm-52.133 40.603a5.69 5.69 0 0 0-5.396-5.966c-14.406-.721-28.217-7.446-37.895-18.452a5.689 5.689 0 0 0-8.542 7.513c11.694 13.299 28.412 21.428 45.868 22.301a5.686 5.686 0 0 0 5.965-5.396zm81.464-133.345c15.644-.293 30.09-5.857 41.777-16.091a5.688 5.688 0 0 0-7.496-8.558c-9.641 8.443-21.568 13.033-34.493 13.275a5.69 5.69 0 0 0-5.581 5.794 5.687 5.687 0 0 0 5.684 5.581l.109-.001z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/tool-dom.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="whitesmoke">
+  <path d="M.2 6.3l3.5 3.6c.1.1.3.2.5.2h.9c.3 0 .5-.2.6-.4.1-.2.1-.5-.1-.7l-3-3.1 3-2.9c.2-.2.3-.5.2-.7-.2-.4-.5-.5-.7-.5h-.9c-.2 0-.4 0-.5.2L.2 5.4c-.3.2-.3.6 0 .9M15.8 9.7l-3.5-3.6c-.1-.1-.3-.2-.5-.2h-.9c-.3 0-.5.2-.6.4-.1.2-.1.5.1.7l3 3.1-3 2.9c-.2.2-.3.5-.2.7.1.3.3.4.6.4h.9c.2 0 .3-.1.5-.2l3.5-3.4c.4-.1.4-.5.1-.8"/>
+</svg>
\ No newline at end of file