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 108037 b0ecba78eb7f778c85684db788f2b54b7b37d3d1
parent 108036 c7ea268e3322b52ff800744bc354563b895a8bad
child 108038 c4397c10018b03a369e325ac3f56b3c5456e7ab5
push id1119
push userprouget@mozilla.com
push dateWed, 26 Sep 2012 09:33:28 +0000
treeherderfx-team@48cfe823a5da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmratcliffe
bugs793725
milestone18.0a1
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>