Bug 852353 - Allow Tilt to support positioning elements at arbitrary depths and with arbitrary depths, r=vporof
authorDave Townsend <dtownsend+bugmail@oxymoronical.com>
Wed, 20 Mar 2013 13:01:39 +0200
changeset 125731 a4742ca5ca32fcec7abad7518274167e765e15fe
parent 125730 f2f57f88ecdfa87c6d272b0e4cdb2897d967e216
child 125732 d855dc68a2024b64c316cb646950363f898dea72
push id25087
push usereakhgari@mozilla.com
push dateThu, 21 Mar 2013 12:27:40 +0000
treeherdermozilla-inbound@be6da6dbf632 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs852353
milestone22.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 852353 - Allow Tilt to support positioning elements at arbitrary depths and with arbitrary depths, r=vporof
browser/devtools/tilt/TiltUtils.jsm
browser/devtools/tilt/TiltVisualizer.jsm
browser/devtools/tilt/TiltWorkerCrafter.js
browser/devtools/tilt/TiltWorkerPicker.js
browser/devtools/tilt/test/browser_tilt_utils05.js
browser/devtools/tilt/test/browser_tilt_utils07.js
--- a/browser/devtools/tilt/TiltUtils.jsm
+++ b/browser/devtools/tilt/TiltUtils.jsm
@@ -8,16 +8,18 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 
+const STACK_THICKNESS = 15;
+
 this.EXPORTED_SYMBOLS = ["TiltUtils"];
 
 /**
  * Module containing various helper functions used throughout Tilt.
  */
 this.TiltUtils = {};
 
 /**
@@ -373,85 +375,124 @@ TiltUtils.DOM = {
   {
     return {
       width: aContentWindow.innerWidth + aContentWindow.scrollMaxX,
       height: aContentWindow.innerHeight + aContentWindow.scrollMaxY
     };
   },
 
   /**
+   * Calculates the position and depth to display a node, this can be overriden
+   * to change the visualization.
+   *
+   * @param {Window} aContentWindow
+   *                 the window content holding the document
+   * @param {Node}   aNode
+   *                 the node to get the position for
+   * @param {Object} aParentPosition
+   *                 the position of the parent node, as returned by this
+   *                 function
+   *
+   * @return {Object} an object describing the node's position in 3D space
+   *                  containing the following properties:
+   *         {Number} top
+   *                  distance along the x axis
+   *         {Number} left
+   *                  distance along the y axis
+   *         {Number} depth
+   *                  distance along the z axis
+   *         {Number} width
+   *                  width of the node
+   *         {Number} height
+   *                  height of the node
+   *         {Number} thickness
+   *                  thickness of the node
+   */
+  getNodePosition: function TUD_getNodePosition(aContentWindow, aNode,
+                                                aParentPosition) {
+    // get the x, y, width and height coordinates of the node
+    let coord = LayoutHelpers.getRect(aNode, aContentWindow);
+    if (!coord) {
+      return null;
+    }
+
+    coord.depth = aParentPosition ? (aParentPosition.depth + aParentPosition.thickness) : 0;
+    coord.thickness = STACK_THICKNESS;
+
+    return coord;
+  },
+
+  /**
    * Traverses a document object model & calculates useful info for each node.
    *
    * @param {Window} aContentWindow
    *                 the window content holding the document
    * @param {Object} aProperties
    *                 optional, an object containing the following properties:
    *        {Object} invisibleElements
    *                 elements which should be ignored
    *        {Number} minSize
    *                 the minimum dimensions needed for a node to be traversed
    *        {Number} maxX
    *                 the maximum left position of an element
    *        {Number} maxY
    *                 the maximum top position of an element
    *
-   * @return {Array} list containing nodes depths, coordinates and local names
+   * @return {Array} list containing nodes positions and local names
    */
   traverse: function TUD_traverse(aContentWindow, aProperties)
   {
     // make sure the properties parameter is a valid object
     aProperties = aProperties || {};
 
     let aInvisibleElements = aProperties.invisibleElements || {};
     let aMinSize = aProperties.minSize || -1;
     let aMaxX = aProperties.maxX || Number.MAX_VALUE;
     let aMaxY = aProperties.maxY || Number.MAX_VALUE;
 
     let nodes = aContentWindow.document.childNodes;
     let store = { info: [], nodes: [] };
     let depth = 0;
 
-    while (nodes.length) {
-      let queue = [];
+    let queue = [
+      { parentPosition: null, nodes: aContentWindow.document.childNodes }
+    ]
 
-      for (let i = 0, len = nodes.length; i < len; i++) {
-        let node = nodes[i];
+    while (queue.length) {
+      let { nodes, parentPosition } = queue.shift();
 
+      for (let node of nodes) {
         // skip some nodes to avoid visualization meshes that are too bloated
         let name = node.localName;
         if (!name || aInvisibleElements[name]) {
           continue;
         }
 
-        // get the x, y, width and height coordinates of the node
-        let coord = LayoutHelpers.getRect(node, aContentWindow);
+        let coord = this.getNodePosition(aContentWindow, node, parentPosition);
         if (!coord) {
           continue;
         }
 
         // the maximum size slices the traversal where needed
         if (coord.left > aMaxX || coord.top > aMaxY) {
           continue;
         }
 
         // use this node only if it actually has visible dimensions
         if (coord.width > aMinSize && coord.height > aMinSize) {
 
           // save the necessary details into a list to be returned later
-          store.info.push({ depth: depth, coord: coord, name: name });
+          store.info.push({ coord: coord, name: name });
           store.nodes.push(node);
         }
 
-        // prepare the queue array
-        Array.prototype.push.apply(queue, name === "iframe" || name === "frame" ?
-                                          node.contentDocument.childNodes :
-                                          node.childNodes);
+        let childNodes = (name === "iframe" || name === "frame") ? node.contentDocument.childNodes : node.childNodes;
+        if (childNodes.length > 0)
+          queue.push({ parentPosition: coord, nodes: childNodes });
       }
-      nodes = queue;
-      depth++;
     }
 
     return store;
   }
 };
 
 /**
  * Binds a new owner object to the child functions.
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -24,17 +24,16 @@ const INVISIBLE_ELEMENTS = {
 
 // a node is represented in the visualization mesh as a rectangular stack
 // of 5 quads composed of 12 vertices; we draw these as triangles using an
 // index buffer of 12 unsigned int elements, obviously one for each vertex;
 // if a webpage has enough nodes to overflow the index buffer elements size,
 // weird things may happen; thus, when necessary, we'll split into groups
 const MAX_GROUP_NODES = Math.pow(2, Uint16Array.BYTES_PER_ELEMENT * 8) / 12 - 1;
 
-const STACK_THICKNESS = 15;
 const WIREFRAME_COLOR = [0, 0, 0, 0.25];
 const INTRO_TRANSITION_DURATION = 1000;
 const OUTRO_TRANSITION_DURATION = 800;
 const INITIAL_Z_TRANSLATION = 400;
 const MOVE_INTO_VIEW_ACCURACY = 50;
 
 const MOUSE_CLICK_THRESHOLD = 10;
 const MOUSE_INTRO_DELAY = 200;
@@ -730,17 +729,16 @@ TiltVisualizer.Presenter.prototype = {
     worker.addEventListener("message", function TVP_onMessage(event) {
       this._setupMesh(event.data);
     }.bind(this), false);
 
     // calculate necessary information regarding vertices, texture coordinates
     // etc. in a separate thread, as this process may take a while
     worker.postMessage({
       maxGroupNodes: MAX_GROUP_NODES,
-      thickness: STACK_THICKNESS,
       style: TiltVisualizerStyle.nodes,
       texWidth: this._texture.width,
       texHeight: this._texture.height,
       nodesInfo: this._traverseData.info
     });
   },
 
   /**
@@ -865,22 +863,22 @@ TiltVisualizer.Presenter.prototype = {
     highlight.fill = style[info.name] || style.highlight.defaultFill;
     highlight.stroke = style.highlight.defaultStroke;
     highlight.strokeWeight = style.highlight.defaultStrokeWeight;
 
     let x = info.coord.left;
     let y = info.coord.top;
     let w = info.coord.width;
     let h = info.coord.height;
-    let z = info.depth;
+    let z = info.coord.depth + info.coord.thickness;
 
-    vec3.set([x,     y,     z * STACK_THICKNESS], highlight.v0);
-    vec3.set([x + w, y,     z * STACK_THICKNESS], highlight.v1);
-    vec3.set([x + w, y + h, z * STACK_THICKNESS], highlight.v2);
-    vec3.set([x,     y + h, z * STACK_THICKNESS], highlight.v3);
+    vec3.set([x,     y,     z], highlight.v0);
+    vec3.set([x + w, y,     z], highlight.v1);
+    vec3.set([x + w, y + h, z], highlight.v2);
+    vec3.set([x,     y + h, z], highlight.v3);
 
     this._currentSelection = aNodeIndex;
 
     // if something is highlighted, make sure it's inside the current viewport;
     // the point which should be moved into view is considered the center [x, y]
     // position along the top edge of the currently selected node
 
     if (aFlags && aFlags.indexOf("moveIntoView") !== -1)
@@ -967,17 +965,16 @@ TiltVisualizer.Presenter.prototype = {
     let height = this._renderer.height * zoom;
     x *= zoom;
     y *= zoom;
 
     // create a ray following the mouse direction from the near clipping plane
     // to the far clipping plane, to check for intersections with the mesh,
     // and do all the heavy lifting in a separate thread
     worker.postMessage({
-      thickness: STACK_THICKNESS,
       vertices: this._meshData.allVertices,
 
       // create the ray destined for 3D picking
       ray: vec3.createRay([x, y, 0], [x, y, 1], [0, 0, width, height],
         this._meshStacks.mvMatrix,
         this._meshStacks.projMatrix)
     });
   },
--- a/browser/devtools/tilt/TiltWorkerCrafter.js
+++ b/browser/devtools/tilt/TiltWorkerCrafter.js
@@ -1,28 +1,27 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set 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";
 
 /**
- * Given the initialization data (thickness, sizes and information about
+ * Given the initialization data (sizes and information about
  * each DOM node) this worker sends back the arrays representing
  * vertices, texture coords, colors, indices and all the needed data for
  * rendering the DOM visualization mesh.
  *
  * Used in the TiltVisualization.Presenter object.
  */
 self.onmessage = function TWC_onMessage(event)
 {
   let data = event.data;
   let maxGroupNodes = parseInt(data.maxGroupNodes);
-  let thickness = data.thickness;
   let style = data.style;
   let texWidth = data.texWidth;
   let texHeight = data.texHeight;
   let nodesInfo = data.nodesInfo;
 
   let mesh = {
     allVertices: [],
     groups: [],
@@ -50,21 +49,20 @@ self.onmessage = function TWC_onMessage(
       texCoord = [];
       color = [];
       stacksIndices = [];
       wireframeIndices = [];
       index = 0;
     }
 
     let info = nodesInfo[n];
-    let depth = info.depth;
     let coord = info.coord;
 
     // calculate the stack x, y, z, width and height coordinates
-    let z = depth * thickness;
+    let z = coord.depth + coord.thickness;
     let y = coord.top;
     let x = coord.left;
     let w = coord.width;
     let h = coord.height;
 
     // the maximum texture size slices the visualization mesh where needed
     if (x + w > texWidth) {
       w = texWidth - x;
@@ -75,17 +73,17 @@ self.onmessage = function TWC_onMessage(
 
     x += self.random.next();
     y += self.random.next();
     w -= self.random.next() * 0.1;
     h -= self.random.next() * 0.1;
 
     let xpw = x + w;
     let yph = y + h;
-    let zmt = z - thickness;
+    let zmt = coord.depth;
 
     let xotw = x / texWidth;
     let yoth = y / texHeight;
     let xpwotw = xpw / texWidth;
     let yphoth = yph / texHeight;
 
     // calculate the margin fill color
     let fill = style[info.name] || style.highlight.defaultFill;
@@ -152,17 +150,17 @@ self.onmessage = function TWC_onMessage(
     // compute the stack indices
     stacksIndices.unshift(i,    ip1,  ip2,  i,    ip2,  ip3,
                           ip8,  ip9,  ip5,  ip8,  ip5,  ip4,
                           ip7,  ip6,  ip10, ip7,  ip10, ip11,
                           ip8,  ip4,  ip7,  ip8,  ip7,  ip11,
                           ip5,  ip9,  ip10, ip5,  ip10, ip6);
 
     // compute the wireframe indices
-    if (depth !== 0) {
+    if (coord.thickness !== 0) {
       wireframeIndices.unshift(i,    ip1, ip1,  ip2,
                                ip2,  ip3, ip3,  i,
                                ip8,  i,   ip9,  ip1,
                                ip11, ip3, ip10, ip2);
     }
 
     // there are 12 vertices in a stack representing a node
     index += 12;
--- a/browser/devtools/tilt/TiltWorkerPicker.js
+++ b/browser/devtools/tilt/TiltWorkerPicker.js
@@ -9,17 +9,16 @@
  * This worker handles picking, given a set of vertices and a ray (calculates
  * the intersection points and offers back information about the closest hit).
  *
  * Used in the TiltVisualization.Presenter object.
  */
 self.onmessage = function TWP_onMessage(event)
 {
   let data = event.data;
-  let thickness = data.thickness;
   let vertices = data.vertices;
   let ray = data.ray;
 
   let intersection = null;
   let hit = [];
 
   // calculates the squared distance between two points
   function dsq(p1, p2) {
@@ -30,26 +29,26 @@ self.onmessage = function TWP_onMessage(
     return xd * xd + yd * yd + zd * zd;
   }
 
   // check each stack face in the visualization mesh for intersections with
   // the mouse ray (using a ray picking algorithm)
   for (let i = 0, len = vertices.length; i < len; i += 36) {
 
     // the front quad
-    let v0f = [vertices[i],     vertices[i + 1],  vertices[i + 2]];
-    let v1f = [vertices[i + 3], vertices[i + 4],  vertices[i + 5]];
-    let v2f = [vertices[i + 6], vertices[i + 7],  vertices[i + 8]];
-    let v3f = [vertices[i + 9], vertices[i + 10], vertices[i + 11]];
+    let v0f = [vertices[i],      vertices[i + 1],  vertices[i + 2]];
+    let v1f = [vertices[i + 3],  vertices[i + 4],  vertices[i + 5]];
+    let v2f = [vertices[i + 6],  vertices[i + 7],  vertices[i + 8]];
+    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];
+    let v0b = [vertices[i + 24], vertices[i + 25], vertices[i + 26]];
+    let v1b = [vertices[i + 27], vertices[i + 28], vertices[i + 29]];
+    let v2b = [vertices[i + 30], vertices[i + 31], vertices[i + 32]];
+    let v3b = [vertices[i + 33], vertices[i + 34], vertices[i + 35]];
 
     // 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
--- a/browser/devtools/tilt/test/browser_tilt_utils05.js
+++ b/browser/devtools/tilt/test/browser_tilt_utils05.js
@@ -1,12 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+const STACK_THICKNESS = 15;
+
 function init(callback) {
   let iframe = gBrowser.ownerDocument.createElement("iframe");
 
   iframe.addEventListener("load", function onLoad() {
     iframe.removeEventListener("load", onLoad, true);
     callback(iframe);
 
     gBrowser.parentNode.removeChild(iframe);
@@ -66,42 +68,31 @@ function test() {
     is(nodeCoordinates.width, 123,
       "The node coordinates width value wasn't calculated correctly.");
     is(nodeCoordinates.height, 456,
       "The node coordinates height value wasn't calculated correctly.");
 
 
     let store = dom.traverse(iframe.contentWindow);
 
-    is(store.nodes.length, 7,
+    let expected = [
+      { name: "html",   depth: 0 * STACK_THICKNESS },
+      { name: "head",   depth: 1 * STACK_THICKNESS },
+      { name: "body",   depth: 1 * STACK_THICKNESS },
+      { name: "style",  depth: 2 * STACK_THICKNESS },
+      { name: "script", depth: 2 * STACK_THICKNESS },
+      { name: "div",    depth: 2 * STACK_THICKNESS },
+      { name: "span",   depth: 3 * STACK_THICKNESS },
+    ];
+
+    is(store.nodes.length, expected.length,
       "The traverse() function didn't walk the correct number of nodes.");
-    is(store.info.length, 7,
+    is(store.info.length, expected.length,
       "The traverse() function didn't examine the correct number of nodes.");
-    is(store.info[0].name, "html",
-      "the 1st traversed node isn't the expected one.");
-    is(store.info[0].depth, 0,
-      "the 1st traversed node doesn't have the expected depth.");
-    is(store.info[1].name, "head",
-      "the 2nd traversed node isn't the expected one.");
-    is(store.info[1].depth, 1,
-      "the 2nd traversed node doesn't have the expected depth.");
-    is(store.info[2].name, "body",
-      "the 3rd traversed node isn't the expected one.");
-    is(store.info[2].depth, 1,
-      "the 3rd traversed node doesn't have the expected depth.");
-    is(store.info[3].name, "style",
-      "the 4th traversed node isn't the expected one.");
-    is(store.info[3].depth, 2,
-      "the 4th traversed node doesn't have the expected depth.");
-    is(store.info[4].name, "script",
-      "the 5th traversed node isn't the expected one.");
-    is(store.info[4].depth, 2,
-      "the 5th traversed node doesn't have the expected depth.");
-    is(store.info[5].name, "div",
-      "the 6th traversed node isn't the expected one.");
-    is(store.info[5].depth, 2,
-      "the 6th traversed node doesn't have the expected depth.");
-    is(store.info[6].name, "span",
-      "the 7th traversed node isn't the expected one.");
-    is(store.info[6].depth, 3,
-      "the 7th traversed node doesn't have the expected depth.");
+
+    for (let i = 0; i < expected.length; i++) {
+      is(store.info[i].name, expected[i].name,
+        "traversed node " + (i + 1) + " isn't the expected one.");
+      is(store.info[i].coord.depth, expected[i].depth,
+        "traversed node " + (i + 1) + " doesn't have the expected depth.");
+    }
   });
 }
--- a/browser/devtools/tilt/test/browser_tilt_utils07.js
+++ b/browser/devtools/tilt/test/browser_tilt_utils07.js
@@ -1,12 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+const STACK_THICKNESS = 15;
+
 function init(callback) {
   let iframe = gBrowser.ownerDocument.createElement("iframe");
 
   iframe.addEventListener("load", function onLoad() {
     iframe.removeEventListener("load", onLoad, true);
     callback(iframe);
 
     gBrowser.parentNode.removeChild(iframe);
@@ -115,78 +117,40 @@ function test() {
     is(nodeCoordinates.width, 123,
       "The node coordinates width value wasn't calculated correctly.");
     is(nodeCoordinates.height, 456,
       "The node coordinates height value wasn't calculated correctly.");
 
 
     let store = dom.traverse(iframe.contentWindow);
 
-    is(store.nodes.length, 16,
+    let expected = [
+      { name: "html",   depth: 0 * STACK_THICKNESS },
+      { name: "head",   depth: 1 * STACK_THICKNESS },
+      { name: "body",   depth: 1 * STACK_THICKNESS },
+      { name: "div",    depth: 2 * STACK_THICKNESS },
+      { name: "span",   depth: 2 * STACK_THICKNESS },
+      { name: "iframe", depth: 2 * STACK_THICKNESS },
+      { name: "span",   depth: 2 * STACK_THICKNESS },
+      { name: "iframe", depth: 2 * STACK_THICKNESS },
+      { name: "html",   depth: 3 * STACK_THICKNESS },
+      { name: "html",   depth: 3 * STACK_THICKNESS },
+      { name: "head",   depth: 4 * STACK_THICKNESS },
+      { name: "body",   depth: 4 * STACK_THICKNESS },
+      { name: "head",   depth: 4 * STACK_THICKNESS },
+      { name: "body",   depth: 4 * STACK_THICKNESS },
+      { name: "span",   depth: 5 * STACK_THICKNESS },
+      { name: "div",    depth: 5 * STACK_THICKNESS },
+    ];
+
+    is(store.nodes.length, expected.length,
       "The traverse() function didn't walk the correct number of nodes.");
-    is(store.info.length, 16,
+    is(store.info.length, expected.length,
       "The traverse() function didn't examine the correct number of nodes.");
-    is(store.info[0].name, "html",
-      "the 1st traversed node isn't the expected one.");
-    is(store.info[0].depth, 0,
-      "the 1st traversed node doesn't have the expected depth.");
-    is(store.info[1].name, "head",
-      "the 2nd traversed node isn't the expected one.");
-    is(store.info[1].depth, 1,
-      "the 2nd traversed node doesn't have the expected depth.");
-    is(store.info[2].name, "body",
-      "the 3rd traversed node isn't the expected one.");
-    is(store.info[2].depth, 1,
-      "the 3rd traversed node doesn't have the expected depth.");
-    is(store.info[3].name, "div",
-      "the 4th traversed node isn't the expected one.");
-    is(store.info[3].depth, 2,
-      "the 4th traversed node doesn't have the expected depth.");
-    is(store.info[4].name, "span",
-      "the 5th traversed node isn't the expected one.");
-    is(store.info[4].depth, 2,
-      "the 5th traversed node doesn't have the expected depth.");
-    is(store.info[5].name, "iframe",
-      "the 6th traversed node isn't the expected one.");
-    is(store.info[5].depth, 2,
-      "the 6th traversed node doesn't have the expected depth.");
-    is(store.info[6].name, "span",
-      "the 7th traversed node isn't the expected one.");
-    is(store.info[6].depth, 2,
-      "the 7th traversed node doesn't have the expected depth.");
-    is(store.info[7].name, "iframe",
-      "the 8th traversed node isn't the expected one.");
-    is(store.info[7].depth, 2,
-      "the 8th traversed node doesn't have the expected depth.");
-    is(store.info[8].name, "html",
-      "the 9th traversed node isn't the expected one.");
-    is(store.info[8].depth, 3,
-      "the 9th traversed node doesn't have the expected depth.");
-    is(store.info[9].name, "html",
-      "the 10th traversed node isn't the expected one.");
-    is(store.info[9].depth, 3,
-      "the 10th traversed node doesn't have the expected depth.");
-    is(store.info[10].name, "head",
-      "the 11th traversed node isn't the expected one.");
-    is(store.info[10].depth, 4,
-      "the 11th traversed node doesn't have the expected depth.");
-    is(store.info[11].name, "body",
-      "the 12th traversed node isn't the expected one.");
-    is(store.info[11].depth, 4,
-      "the 12th traversed node doesn't have the expected depth.");
-    is(store.info[12].name, "head",
-      "the 13th traversed node isn't the expected one.");
-    is(store.info[12].depth, 4,
-      "the 13th traversed node doesn't have the expected depth.");
-    is(store.info[13].name, "body",
-      "the 14th traversed node isn't the expected one.");
-    is(store.info[13].depth, 4,
-      "the 14th traversed node doesn't have the expected depth.");
-    is(store.info[14].name, "span",
-      "the 15th traversed node isn't the expected one.");
-    is(store.info[14].depth, 5,
-      "the 15th traversed node doesn't have the expected depth.");
-    is(store.info[15].name, "div",
-      "the 16th traversed node isn't the expected one.");
-    is(store.info[15].depth, 5,
-      "the 16th traversed node doesn't have the expected depth.");
+
+    for (let i = 0; i < expected.length; i++) {
+      is(store.info[i].name, expected[i].name,
+        "traversed node " + (i + 1) + " isn't the expected one.");
+      is(store.info[i].coord.depth, expected[i].depth,
+        "traversed node " + (i + 1) + " doesn't have the expected depth.");
+    }
   });
 }