Bug 793725 - [markup panel] Add a preview of the DOM tree. r=mratcliffe
authorPaul Rouget <paul@mozilla.com>
Tue, 25 Sep 2012 17:33:46 +0100
changeset 108193 b0ecba78eb7f778c85684db788f2b54b7b37d3d1
parent 108192 c7ea268e3322b52ff800744bc354563b895a8bad
child 108194 c4397c10018b03a369e325ac3f56b3c5456e7ab5
push id23544
push userttaubert@mozilla.com
push dateThu, 27 Sep 2012 07:12:21 +0000
treeherdermozilla-central@b038e9e2023f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmratcliffe
bugs793725
milestone18.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 793725 - [markup panel] Add a preview of the DOM tree. r=mratcliffe
browser/app/profile/firefox.js
browser/devtools/markupview/MarkupView.jsm
browser/devtools/markupview/markup-view.css
browser/devtools/markupview/markup-view.xhtml
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1010,16 +1010,17 @@ pref("devtools.gcli.allowSet", false);
 pref("devtools.commands.dir", "");
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.htmlHeight", 112);
 pref("devtools.inspector.htmlPanelOpen", false);
 pref("devtools.inspector.sidebarOpen", false);
 pref("devtools.inspector.activeSidebar", "ruleview");
+pref("devtools.inspector.markupPreview", true);
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", true);
 pref("devtools.layoutview.open", false);
 
 // Enable the Responsive UI tool
 pref("devtools.responsiveUI.enabled", true);
 
--- a/browser/devtools/markupview/MarkupView.jsm
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -6,22 +6,25 @@
 
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 // Page size for pageup/pagedown
 const PAGE_SIZE = 10;
 
+const PREVIEW_AREA = 700;
+
 var EXPORTED_SYMBOLS = ["MarkupView"];
 
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource:///modules/devtools/CssRuleView.jsm");
 Cu.import("resource:///modules/devtools/Templater.jsm");
-Cu.import("resource:///modules/devtools/Undo.jsm")
+Cu.import("resource:///modules/devtools/Undo.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 /**
  * Vocabulary for the purposes of this file:
  *
  * MarkupContainer - the structure that holds an editor and its
  *  immediate children in the markup panel.
  * Node - A content node.
  * object.elt - A UI element in the markup panel.
@@ -54,16 +57,18 @@ function MarkupView(aInspector, aFrame)
   this._inspector.on("select", this._boundSelect);
   this._onSelect();
 
   this._boundKeyDown = this._onKeyDown.bind(this);
   this._frame.addEventListener("keydown", this._boundKeyDown, false);
 
   this._boundFocus = this._onFocus.bind(this);
   this._frame.addEventListener("focus", this._boundFocus, false);
+
+  this._initPreview();
 }
 
 MarkupView.prototype = {
   _selectedContainer: null,
 
   /**
    * Return the selected node.
    */
@@ -468,31 +473,115 @@ MarkupView.prototype = {
   /**
    * Tear down the markup panel.
    */
   destroy: function MT_destroy()
   {
     this.undo.destroy();
     delete this.undo;
 
-    this._frame.addEventListener("focus", this._boundFocus, false);
+    this._frame.removeEventListener("focus", this._boundFocus, false);
     delete this._boundFocus;
 
+    this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true);
+    this._frame.contentWindow.removeEventListener("resize", this._boundUpdatePreview, true);
+    this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true);
+    this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true);
+    delete this._boundUpdatePreview;
+
     this._frame.removeEventListener("keydown", this._boundKeyDown, true);
     delete this._boundKeyDown;
 
     this._inspector.off("select", this._boundSelect);
     delete this._boundSelect;
 
     delete this._elt;
 
     delete this._containers;
     this._observer.disconnect();
     delete this._observer;
-  }
+  },
+
+  /**
+   * Initialize the preview panel.
+   */
+  _initPreview: function MT_initPreview()
+  {
+    if (!Services.prefs.getBoolPref("devtools.inspector.markupPreview")) {
+      return;
+    }
+
+    this._previewBar = this.doc.querySelector("#previewbar");
+    this._preview = this.doc.querySelector("#preview");
+    this._viewbox = this.doc.querySelector("#viewbox");
+
+    this._previewBar.classList.remove("disabled");
+
+    this._previewWidth = this._preview.getBoundingClientRect().width;
+
+    this._boundResizePreview = this._resizePreview.bind(this);
+    this._frame.contentWindow.addEventListener("resize", this._boundResizePreview, true);
+    this._frame.contentWindow.addEventListener("overflow", this._boundResizePreview, true);
+    this._frame.contentWindow.addEventListener("underflow", this._boundResizePreview, true);
+
+    this._boundUpdatePreview = this._updatePreview.bind(this);
+    this._frame.contentWindow.addEventListener("scroll", this._boundUpdatePreview, true);
+    this._updatePreview();
+  },
+
+
+  /**
+   * Move the preview viewbox.
+   */
+  _updatePreview: function MT_updatePreview()
+  {
+    let win = this._frame.contentWindow;
+
+    if (win.scrollMaxY == 0) {
+      this._previewBar.classList.add("disabled");
+      return;
+    }
+
+    this._previewBar.classList.remove("disabled");
+
+    let ratio = this._previewWidth / PREVIEW_AREA;
+    let width = ratio * win.innerWidth;
+
+    let height = ratio * (win.scrollMaxY + win.innerHeight);
+    let scrollTo
+    if (height >= win.innerHeight) {
+      scrollTo = -(height - win.innerHeight) * (win.scrollY / win.scrollMaxY);
+      this._previewBar.setAttribute("style", "height:" + height + "px;transform:translateY(" + scrollTo + "px)");
+    } else {
+      this._previewBar.setAttribute("style", "height:100%");
+    }
+
+    let bgSize = ~~width + "px " + ~~height + "px";
+    this._preview.setAttribute("style", "background-size:" + bgSize);
+
+    let height = ~~(win.innerHeight * ratio) + "px";
+    let top = ~~(win.scrollY * ratio) + "px";
+    this._viewbox.setAttribute("style", "height:" + height + ";transform: translateY(" + top + ")");
+  },
+
+  /**
+   * Hide the preview while resizing, to avoid slowness.
+   */
+  _resizePreview: function MT_resizePreview()
+  {
+    let win = this._frame.contentWindow;
+    this._previewBar.classList.add("hide");
+    win.clearTimeout(this._resizePreviewTimeout);
+
+    win.setTimeout(function() {
+      this._updatePreview();
+      this._previewBar.classList.remove("hide");
+    }.bind(this), 1000);
+  },
+
 };
 
 
 /**
  * The main structure for storing a document node in the markup
  * tree.  Manages creation of the editor for the node and
  * a <ul> for placing child elements, and expansion/collapsing
  * of the element.
--- a/browser/devtools/markupview/markup-view.css
+++ b/browser/devtools/markupview/markup-view.css
@@ -14,8 +14,50 @@ ul.children:not([expanded]) {
   display: inline-block;
 }
 
 .newattr {
   display: inline-block;
   width: 1em;
   height: 1ex;
 }
+
+#root {
+  margin-right: 80px;
+}
+
+/* Preview */
+
+#previewbar {
+  position: fixed;
+  top: 0;
+  right: 0;
+  width: 90px;
+  background: black;
+  border-left: 1px solid #333;
+  border-bottom: 1px solid #333;
+  overflow: hidden;
+}
+
+#preview {
+  position: absolute;
+  top: 0;
+  right: 5px;
+  width: 80px;
+  height: 100%;
+  background-image: -moz-element(#root);
+  background-repeat: no-repeat;
+}
+
+#previewbar.hide,
+#previewbar.disabled {
+  display: none;
+}
+
+#viewbox {
+  position: absolute;
+  top: 0;
+  right: 5px;
+  width: 80px;
+  border: 1px dashed #888;
+  background: rgba(205,205,255,0.2);
+  outline: 1px solid transparent;
+}
--- a/browser/devtools/markupview/markup-view.xhtml
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -8,28 +8,32 @@
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet" href="chrome://browser/content/devtools/markup-view.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://browser/skin/devtools/markup-view.css" type="text/css"/>
 </head>
 <body role="application">
   <div id="root"></div>
   <div id="templates" style="display:none">
-  	<ul>
-  	  <li id="template-container" save="${elt}" class="container"><span save="${expander}" class="expander"></span><span save="${codeBox}" class="codebox"><ul save="${children}" class="children"></ul></span></li>
+    <ul>
+      <li id="template-container" save="${elt}" class="container"><span save="${expander}" class="expander"></span><span save="${codeBox}" class="codebox"><ul save="${children}" class="children"></ul></span></li>
     </ul>
 
     <span id="template-element" save="${elt}" class="editor"><span>&lt;</span><span save="${tag}" class="tagname"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span>&gt;</span>
 
     <span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attrname"></span>=&quot;<span save="${val}" class="attrvalue"></span>&quot;</span></span>
 
     <span id="template-text" save="${elt}" class="editor">
       <pre save="${value}" style="display:inline-block;" tabindex="0"></pre>
     </span>
 
     <span id="template-comment" save="${elt}" class="editor comment">
       <span>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span>
     </span>
 
     <span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname"></span>&gt;</span>
    </div>
+   <div id="previewbar" class="disabled">
+     <div id="preview"/>
+     <div id="viewbox"/>
+   </div>
 </body>
 </html>