Bug 715647 - I want to be able to remove nodes from the Tilt view; r=rcampbell
authorVictor Porof <vporof@mozilla.com>
Wed, 25 Jan 2012 10:04:15 +0200
changeset 86597 397db1eba037d38be2b0e1c6b90b0b1cf6b55bfe
parent 86568 edf8075b0333a0ed7be2d77b1fd06bc87ca7bdec
child 86598 586448c078ef739e1009ca6739ade5c87364d877
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs715647
milestone12.0a1
Bug 715647 - I want to be able to remove nodes from the Tilt view; r=rcampbell
browser/devtools/tilt/TiltVisualizer.jsm
browser/devtools/tilt/TiltWorkerPicker.js
browser/devtools/tilt/test/Makefile.in
browser/devtools/tilt/test/browser_tilt_picking.js
browser/devtools/tilt/test/browser_tilt_picking_delete.js
browser/devtools/tilt/test/browser_tilt_picking_highlight01.js
browser/devtools/tilt/test/browser_tilt_picking_highlight02.js
browser/devtools/tilt/test/browser_tilt_picking_highlight03.js
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -475,16 +475,21 @@ TiltVisualizer.Presenter.prototype = {
     // this will be removed once the MOZ_window_region_texture bug #653656
     // is finished; currently just converting the document image to a texture
     // applied to the mesh
     this.texture = new renderer.Texture({
       source: TiltGL.TextureUtils.createContentImage(this.contentWindow,
                                                      this.maxTextureSize),
       format: "RGB"
     });
+
+    if ("function" === typeof this.onSetupTexture) {
+      this.onSetupTexture();
+      this.onSetupTexture = null;
+    }
   },
 
   /**
    * Create the combined mesh representing the document visualization by
    * traversing the document & adding a stack for each node that is drawable.
    *
    * @param {Object} aData
    *                 object containing the necessary mesh verts, texcoord etc.
@@ -497,16 +502,19 @@ TiltVisualizer.Presenter.prototype = {
     TiltUtils.destroyObject(this.meshStacks);
     TiltUtils.destroyObject(this.meshWireframe);
 
     // if the renderer was destroyed, don't continue setup
     if (!renderer || !renderer.context) {
       return;
     }
 
+    // save the mesh data for future use
+    this.meshData = aData;
+
     // create the visualization mesh using the vertices, texture coordinates
     // and indices computed when traversing the document object model
     this.meshStacks = {
       vertices: new renderer.VertexBuffer(aData.vertices, 3),
       texCoord: new renderer.VertexBuffer(aData.texCoord, 2),
       color: new renderer.VertexBuffer(aData.color, 3),
       indices: new renderer.IndexBuffer(aData.stacksIndices)
     };
@@ -519,29 +527,38 @@ TiltVisualizer.Presenter.prototype = {
     };
 
     // if there's no initial selection made, highlight the required node
     if (!this._initialSelection) {
       this._initialSelection = true;
       this.highlightNode(this.inspectorUI.selection);
     }
 
-    let zoom = TiltUtils.getDocumentZoom();
-    let width = Math.min(aData.meshWidth * zoom, renderer.width);
-    let height = Math.min(aData.meshHeight * zoom, renderer.height);
+    if (!this._initialMeshConfiguration) {
+      this._initialMeshConfiguration = true;
+
+      let zoom = TiltUtils.getDocumentZoom();
+      let width = Math.min(aData.meshWidth * zoom, renderer.width);
+      let height = Math.min(aData.meshHeight * zoom, renderer.height);
+
+      // set the necessary mesh offsets
+      this.transforms.offset[0] = -width * 0.5;
+      this.transforms.offset[1] = -height * 0.5;
 
-    // set the necessary mesh offsets
-    this.transforms.offset[0] = -width * 0.5;
-    this.transforms.offset[1] = -height * 0.5;
+      // make sure the canvas is opaque now that the initialization is finished
+      this.canvas.style.background = TiltVisualizerStyle.canvas.background;
 
-    // make sure the canvas is opaque now that the initialization is finished
-    this.canvas.style.background = TiltVisualizerStyle.canvas.background;
+      this.drawVisualization();
+      this.redraw = true;
+    }
 
-    this.drawVisualization();
-    this.redraw = true;
+    if ("function" === typeof this.onSetupMesh) {
+      this.onSetupMesh();
+      this.onSetupMesh = null;
+    }
   },
 
   /**
    * Computes the mesh vertices, texture coordinates etc.
    */
   setupMeshData: function TVP_setupMeshData()
   {
     let renderer = this.renderer;
@@ -621,39 +638,54 @@ TiltVisualizer.Presenter.prototype = {
   /**
    * Picks a stacked dom node at the x and y screen coordinates and highlights
    * the selected node in the mesh.
    *
    * @param {Number} x
    *                 the current horizontal coordinate of the mouse
    * @param {Number} y
    *                 the current vertical coordinate of the mouse
+   * @param {Object} aProperties
+   *                 an object containing the following properties:
+   *      {Function} onpick: function to be called after picking succeeded
+   *      {Function} onfail: function to be called after picking failed
    */
-  highlightNodeAt: function TVP_highlightNodeAt(x, y)
+  highlightNodeAt: function TVP_highlightNodeAt(x, y, aProperties)
   {
+    // make sure the properties parameter is a valid object
+    aProperties = aProperties || {};
+
     // try to pick a mesh node using the current x, y coordinates
     this.pickNode(x, y, {
 
       /**
        * Mesh picking failed (nothing was found for the picked point).
        */
       onfail: function TVP_onHighlightFail()
       {
         this.highlightNodeFor(-1);
+
+        if ("function" === typeof aProperties.onfail) {
+          aProperties.onfail();
+        }
       }.bind(this),
 
       /**
        * Mesh picking succeeded.
        *
        * @param {Object} aIntersection
        *                 object containing the intersection details
        */
       onpick: function TVP_onHighlightPick(aIntersection)
       {
         this.highlightNodeFor(aIntersection.index);
+
+        if ("function" === typeof aProperties.onpick) {
+          aProperties.onpick();
+        }
       }.bind(this)
     });
   },
 
   /**
    * Sets the corresponding highlight coordinates and color based on the
    * information supplied.
    *
@@ -697,16 +729,42 @@ TiltVisualizer.Presenter.prototype = {
     vec3.set([x,     y + h, z * STACK_THICKNESS], highlight.v3);
 
     this._currentSelection = aNodeIndex;
     this.inspectorUI.inspectNode(node, this.contentWindow.innerHeight < y ||
                                        this.contentWindow.pageYOffset > 0);
   },
 
   /**
+   * Deletes a node from the visualization mesh.
+   *
+   * @param {Number} aNodeIndex
+   *                 the index of the node in the this.traverseData array;
+   *                 if not specified, it will default to the current selection
+   */
+  deleteNode: function TVP_deleteNode(aNodeIndex)
+  {
+    // we probably don't want to delete the html or body node.. just sayin'
+    if ((aNodeIndex = aNodeIndex || this._currentSelection) < 1) {
+      return;
+    }
+
+    let renderer = this.renderer;
+    let meshData = this.meshData;
+
+    for (let i = 0, k = 36 * aNodeIndex; i < 36; i++) {
+      meshData.vertices[i + k] = 0;
+    }
+
+    this.meshStacks.vertices = new renderer.VertexBuffer(meshData.vertices, 3);
+    this.highlight.disabled = true;
+    this.redraw = true;
+  },
+
+  /**
    * Picks a stacked dom node at the x and y screen coordinates and issues
    * a callback function with the found intersection.
    *
    * @param {Number} x
    *                 the current horizontal coordinate of the mouse
    * @param {Number} y
    *                 the current vertical coordinate of the mouse
    * @param {Object} aProperties
@@ -918,17 +976,16 @@ TiltVisualizer.Controller.prototype = {
   addEventListeners: function TVC_addEventListeners()
   {
     let canvas = this.canvas;
     let presenter = this.presenter;
 
     // bind commonly used mouse and keyboard events with the controller
     canvas.addEventListener("mousedown", this.onMouseDown, false);
     canvas.addEventListener("mouseup", this.onMouseUp, false);
-    canvas.addEventListener("click", this.onMouseClick, false);
     canvas.addEventListener("mousemove", this.onMouseMove, false);
     canvas.addEventListener("mouseover", this.onMouseOver, false);
     canvas.addEventListener("mouseout", this.onMouseOut, false);
     canvas.addEventListener("MozMousePixelScroll", this.onMozScroll, false);
     canvas.addEventListener("keydown", this.onKeyDown, false);
     canvas.addEventListener("keyup", this.onKeyUp, false);
     canvas.addEventListener("blur", this.onBlur, false);
 
@@ -941,17 +998,16 @@ TiltVisualizer.Controller.prototype = {
    */
   removeEventListeners: function TVC_removeEventListeners()
   {
     let canvas = this.canvas;
     let presenter = this.presenter;
 
     canvas.removeEventListener("mousedown", this.onMouseDown, false);
     canvas.removeEventListener("mouseup", this.onMouseUp, false);
-    canvas.removeEventListener("click", this.onMouseClick, false);
     canvas.removeEventListener("mousemove", this.onMouseMove, false);
     canvas.removeEventListener("mouseover", this.onMouseOver, false);
     canvas.removeEventListener("mouseout", this.onMouseOut, false);
     canvas.removeEventListener("MozMousePixelScroll", this.onMozScroll, false);
     canvas.removeEventListener("keydown", this.onKeyDown, false);
     canvas.removeEventListener("keyup", this.onKeyUp, false);
     canvas.removeEventListener("blur", this.onBlur, false);
 
@@ -974,58 +1030,45 @@ TiltVisualizer.Controller.prototype = {
    */
   onMouseDown: function TVC_onMouseDown(e)
   {
     e.target.focus();
     e.preventDefault();
     e.stopPropagation();
 
     // calculate x and y coordinates using using the client and target offset
+    let button = e.which;
     this._downX = e.clientX - e.target.offsetLeft;
     this._downY = e.clientY - e.target.offsetTop;
 
-    this.arcball.mouseDown(this._downX, this._downY, e.which);
+    this.arcball.mouseDown(this._downX, this._downY, button);
   },
 
   /**
    * Called every time a mouse button is released.
    */
   onMouseUp: function TVC_onMouseUp(e)
   {
     e.preventDefault();
     e.stopPropagation();
 
     // calculate x and y coordinates using using the client and target offset
     let button = e.which;
     let upX = e.clientX - e.target.offsetLeft;
     let upY = e.clientY - e.target.offsetTop;
 
-    this.arcball.mouseUp(upX, upY, button);
-  },
-
-  /**
-   * Called every time a mouse button is clicked.
-   */
-  onMouseClick: function TVC_onMouseClick(e)
-  {
-    e.preventDefault();
-    e.stopPropagation();
-
-    // calculate x and y coordinates using using the client and target offset
-    let button = e.which;
-    let clickX = e.clientX - e.target.offsetLeft;
-    let clickY = e.clientY - e.target.offsetTop;
-
     // a click in Tilt is issued only when the mouse pointer stays in
     // relatively the same position
-    if (Math.abs(this._downX - clickX) < MOUSE_CLICK_THRESHOLD &&
-        Math.abs(this._downY - clickY) < MOUSE_CLICK_THRESHOLD) {
+    if (Math.abs(this._downX - upX) < MOUSE_CLICK_THRESHOLD &&
+        Math.abs(this._downY - upY) < MOUSE_CLICK_THRESHOLD) {
 
-      this.presenter.highlightNodeAt(clickX, clickY);
+      this.presenter.highlightNodeAt(upX, upY);
     }
+
+    this.arcball.mouseUp(upX, upY, button);
   },
 
   /**
    * Called every time the mouse moves.
    */
   onMouseMove: function TVC_onMouseMove(e)
   {
     e.preventDefault();
@@ -1093,16 +1136,19 @@ TiltVisualizer.Controller.prototype = {
   onKeyUp: function TVC_onKeyUp(e)
   {
     let code = e.keyCode || e.which;
 
     if (code === e.DOM_VK_ESCAPE) {
       this.presenter.tiltUI.destroy(this.presenter.tiltUI.currentWindowId, 1);
       return;
     }
+    if (code === e.DOM_VK_X) {
+      this.presenter.deleteNode();
+    }
 
     if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
       e.preventDefault();
       e.stopPropagation();
       this.arcball.keyUp(code);
     }
   },
 
--- a/browser/devtools/tilt/TiltWorkerPicker.js
+++ b/browser/devtools/tilt/TiltWorkerPicker.js
@@ -76,16 +76,21 @@ self.onmessage = function TWP_onMessage(
     let v3f = [vertices[i + 9], vertices[i + 10], vertices[i + 11]];
 
     // the back quad
     let v0b = [v0f[0], v0f[1], v0f[2] - thickness];
     let v1b = [v1f[0], v1f[1], v1f[2] - thickness];
     let v2b = [v2f[0], v2f[1], v2f[2] - thickness];
     let v3b = [v3f[0], v3f[1], v3f[2] - thickness];
 
+    // don't do anything with degenerate quads
+    if (!v0f[0] && !v1f[0] && !v2f[0] && !v3f[0]) {
+      continue;
+    }
+
     // for each triangle in the stack box, check for the intersections
     if (self.intersect(v0f, v1f, v2f, ray, hit) || // front left
         self.intersect(v0f, v2f, v3f, ray, hit) || // front right
         self.intersect(v0b, v1b, v1f, ray, hit) || // left back
         self.intersect(v0b, v1f, v0f, ray, hit) || // left front
         self.intersect(v3f, v2b, v3b, ray, hit) || // right back
         self.intersect(v3f, v2f, v2b, ray, hit) || // right front
         self.intersect(v0b, v0f, v3f, ray, hit) || // top left
--- a/browser/devtools/tilt/test/Makefile.in
+++ b/browser/devtools/tilt/test/Makefile.in
@@ -68,16 +68,21 @@ include $(topsrcdir)/config/rules.mk
 	browser_tilt_gl08.js \
 	browser_tilt_math01.js \
 	browser_tilt_math02.js \
 	browser_tilt_math03.js \
 	browser_tilt_math04.js \
 	browser_tilt_math05.js \
 	browser_tilt_math06.js \
 	browser_tilt_math07.js \
+	browser_tilt_picking.js \
+	browser_tilt_picking_delete.js \
+	browser_tilt_picking_highlight01.js \
+	browser_tilt_picking_highlight02.js \
+	browser_tilt_picking_highlight03.js \
 	browser_tilt_utils01.js \
 	browser_tilt_utils02.js \
 	browser_tilt_utils03.js \
 	browser_tilt_utils04.js \
 	browser_tilt_utils05.js \
 	browser_tilt_utils06.js \
 	browser_tilt_visualizer.js \
 	browser_tilt_zoom.js \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/tilt/test/browser_tilt_picking.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*global ok, is, info, waitForExplicitFinish, finish, gBrowser */
+/*global isTiltEnabled, isWebGLSupported, createTab, createTilt */
+/*global Services, InspectorUI, TILT_DESTROYED */
+"use strict";
+
+function test() {
+  if (!isTiltEnabled()) {
+    info("Skipping picking test because Tilt isn't enabled.");
+    return;
+  }
+  if (!isWebGLSupported()) {
+    info("Skipping picking test because WebGL isn't supported.");
+    return;
+  }
+
+  waitForExplicitFinish();
+
+  createTab(function() {
+    createTilt({
+      onTiltOpen: function(instance)
+      {
+        let presenter = instance.presenter;
+        let canvas = presenter.canvas;
+
+        presenter.onSetupMesh = function() {
+
+          presenter.pickNode(canvas.width / 2, canvas.height / 2, {
+            onpick: function(data)
+            {
+              ok(data.index > 0,
+                "Simply picking a node didn't work properly.");
+              ok(!presenter.highlight.disabled,
+                "After only picking a node, it shouldn't be highlighted.");
+
+              Services.obs.addObserver(cleanup, TILT_DESTROYED, false);
+              InspectorUI.closeInspectorUI();
+            }
+          });
+        };
+      }
+    });
+  });
+}
+
+function cleanup() {
+  Services.obs.removeObserver(cleanup, TILT_DESTROYED);
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/tilt/test/browser_tilt_picking_delete.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*global ok, is, info, waitForExplicitFinish, finish, gBrowser */
+/*global isTiltEnabled, isWebGLSupported, createTab, createTilt */
+/*global Services, InspectorUI, TILT_DESTROYED */
+"use strict";
+
+function test() {
+  if (!isTiltEnabled()) {
+    info("Skipping picking delete test because Tilt isn't enabled.");
+    return;
+  }
+  if (!isWebGLSupported()) {
+    info("Skipping picking delete test because WebGL isn't supported.");
+    return;
+  }
+
+  waitForExplicitFinish();
+
+  createTab(function() {
+    createTilt({
+      onTiltOpen: function(instance)
+      {
+        let presenter = instance.presenter;
+        let canvas = presenter.canvas;
+
+        presenter.onSetupMesh = function() {
+
+          presenter.highlightNodeAt(canvas.width / 2, canvas.height / 2, {
+            onpick: function()
+            {
+              ok(presenter._currentSelection > 0,
+                "Highlighting a node didn't work properly.");
+              ok(!presenter.highlight.disabled,
+                "After highlighting a node, it should be highlighted. D'oh.");
+
+              presenter.deleteNode();
+
+              ok(presenter._currentSelection > 0,
+                "Deleting a node shouldn't change the current selection.");
+              ok(presenter.highlight.disabled,
+                "After deleting a node, it shouldn't be highlighted.");
+
+              let nodeIndex = presenter._currentSelection;
+              let meshData = presenter.meshData;
+
+              for (let i = 0, k = 36 * nodeIndex; i < 36; i++) {
+                is(meshData.vertices[i + k], 0,
+                  "The stack vertices weren't degenerated properly.");
+              }
+
+              Services.obs.addObserver(cleanup, TILT_DESTROYED, false);
+              InspectorUI.closeInspectorUI();
+            }
+          });
+        };
+      }
+    });
+  });
+}
+
+function cleanup() {
+  Services.obs.removeObserver(cleanup, TILT_DESTROYED);
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight01.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*global ok, is, info, waitForExplicitFinish, finish, gBrowser */
+/*global isTiltEnabled, isWebGLSupported, createTab, createTilt */
+/*global Services, InspectorUI, TILT_DESTROYED */
+"use strict";
+
+function test() {
+  if (!isTiltEnabled()) {
+    info("Skipping highlight test because Tilt isn't enabled.");
+    return;
+  }
+  if (!isWebGLSupported()) {
+    info("Skipping highlight test because WebGL isn't supported.");
+    return;
+  }
+
+  waitForExplicitFinish();
+
+  createTab(function() {
+    createTilt({
+      onTiltOpen: function(instance)
+      {
+        let presenter = instance.presenter;
+
+        presenter.onSetupMesh = function() {
+          let contentDocument = presenter.contentWindow.document;
+          let body = contentDocument.getElementsByTagName("body")[0];
+
+          presenter.highlightNode(body);
+
+          ok(presenter._currentSelection > 0,
+            "Highlighting a node didn't work properly.");
+          ok(!presenter.highlight.disabled,
+            "After highlighting a node, it should be highlighted. D'oh.");
+
+          Services.obs.addObserver(cleanup, TILT_DESTROYED, false);
+          InspectorUI.closeInspectorUI();
+        };
+      }
+    });
+  });
+}
+
+function cleanup() {
+  Services.obs.removeObserver(cleanup, TILT_DESTROYED);
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight02.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*global ok, is, info, waitForExplicitFinish, finish, gBrowser */
+/*global isTiltEnabled, isWebGLSupported, createTab, createTilt */
+/*global Services, InspectorUI, TILT_DESTROYED */
+"use strict";
+
+function test() {
+  if (!isTiltEnabled()) {
+    info("Skipping highlight test because Tilt isn't enabled.");
+    return;
+  }
+  if (!isWebGLSupported()) {
+    info("Skipping highlight test because WebGL isn't supported.");
+    return;
+  }
+
+  waitForExplicitFinish();
+
+  createTab(function() {
+    createTilt({
+      onTiltOpen: function(instance)
+      {
+        let presenter = instance.presenter;
+        let canvas = presenter.canvas;
+
+        presenter.onSetupMesh = function() {
+
+          presenter.highlightNodeAt(canvas.width / 2, canvas.height / 2, {
+            onpick: function()
+            {
+              ok(presenter._currentSelection > 0,
+                "Highlighting a node didn't work properly.");
+              ok(!presenter.highlight.disabled,
+                "After highlighting a node, it should be highlighted. D'oh.");
+
+              Services.obs.addObserver(cleanup, TILT_DESTROYED, false);
+              InspectorUI.closeInspectorUI();
+            }
+          });
+        };
+      }
+    });
+  });
+}
+
+function cleanup() {
+  Services.obs.removeObserver(cleanup, TILT_DESTROYED);
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/tilt/test/browser_tilt_picking_highlight03.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*global ok, is, info, waitForExplicitFinish, finish, gBrowser */
+/*global isTiltEnabled, isWebGLSupported, createTab, createTilt */
+/*global Services, InspectorUI, TILT_DESTROYED */
+"use strict";
+
+function test() {
+  if (!isTiltEnabled()) {
+    info("Skipping highlight test because Tilt isn't enabled.");
+    return;
+  }
+  if (!isWebGLSupported()) {
+    info("Skipping highlight test because WebGL isn't supported.");
+    return;
+  }
+
+  waitForExplicitFinish();
+
+  createTab(function() {
+    createTilt({
+      onTiltOpen: function(instance)
+      {
+        let presenter = instance.presenter;
+
+        presenter.onSetupMesh = function() {
+          presenter.highlightNodeFor(1);
+
+          ok(presenter._currentSelection > 0,
+            "Highlighting a node didn't work properly.");
+          ok(!presenter.highlight.disabled,
+            "After highlighting a node, it should be highlighted. D'oh.");
+
+          Services.obs.addObserver(cleanup, TILT_DESTROYED, false);
+          InspectorUI.closeInspectorUI();
+        };
+      }
+    });
+  });
+}
+
+function cleanup() {
+  Services.obs.removeObserver(cleanup, TILT_DESTROYED);
+  gBrowser.removeCurrentTab();
+  finish();
+}