Backed out changeset b617a57d6bf1 (bug 1169439) for OSX debug browser_projecteditor_contextmenu_02.js and browser_projecteditor_menubar_02.js permafail.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 03 Jun 2015 16:12:10 -0400
changeset 247130 dc4023d54436f3a1fd4c93adfdc595aedab5b082
parent 247129 cd451afd6400e9ca96fd9a6062bda0bff19f6af8
child 247131 813e37cf69e3adc4571ef15a83af234c172891bf
push id28854
push userryanvm@gmail.com
push dateThu, 04 Jun 2015 13:24:20 +0000
treeherdermozilla-central@5b4c240e1a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1169439
milestone41.0a1
backs outb617a57d6bf1a18d3f487be2f989feef3dc4a072
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
Backed out changeset b617a57d6bf1 (bug 1169439) for OSX debug browser_projecteditor_contextmenu_02.js and browser_projecteditor_menubar_02.js permafail.
browser/devtools/performance/modules/logic/marker-utils.js
browser/devtools/performance/modules/logic/waterfall-utils.js
browser/devtools/performance/modules/markers.js
browser/devtools/performance/test/browser.ini
browser/devtools/performance/test/browser_waterfall-collapse.js
browser/devtools/performance/test/unit/test_waterfall-utils-collapse-01.js
browser/devtools/performance/test/unit/test_waterfall-utils-collapse-02.js
browser/devtools/performance/test/unit/xpcshell.ini
browser/devtools/performance/views/details-waterfall.js
--- a/browser/devtools/performance/modules/logic/marker-utils.js
+++ b/browser/devtools/performance/modules/logic/marker-utils.js
@@ -275,83 +275,69 @@ const DOM = {
 
     return container;
   }
 };
 
 /**
  * A series of collapsers used by the blueprint. These functions are
  * invoked on a moving window of two markers.
- *
- * A function determining how markers are collapsed together.
- * Invoked with 3 arguments: the current parent marker, the
- * current marker and a method for peeking i markers ahead. If
- * nothing is returned, the marker is added as a standalone entry
- * in the waterfall. Otherwise, an object needs to be returned
- * with the following properties:
- * - toParent: The marker to be made a new parent. Can use the current
- *             marker, becoming a parent itself, or make a new marker-esque
- *             object.
- * - collapse: Whether or not this current marker should be nested within
- *             the current parent.
- * - finalize: Whether or not the current parent should be finalized and popped
- *        off the stack.
  */
+
 const CollapseFunctions = {
-  /**
-   * Combines similar markers that are consecutive into a meta marker.
-   */
   identical: function (parent, curr, peek) {
-    let next = peek(1);
     // If there is a parent marker currently being filled and the current marker
     // should go into the parent marker, make it so.
     if (parent && parent.name == curr.name) {
-      let finalize = next && next.name !== curr.name;
-      return { collapse: true, finalize };
+      return { toParent: parent.name };
     }
     // Otherwise if the current marker is the same type as the next marker type,
     // create a new parent marker containing the current marker.
+    let next = peek(1);
     if (next && curr.name == next.name) {
-      return { toParent: { name: curr.name, start: curr.start }, collapse: true };
+      return { toParent: curr.name };
     }
   },
 
-  /**
-   * Combines similar markers that are close to each other in time into a meta marker.
-   */
   adjacent: function (parent, curr, peek) {
     let next = peek(1);
     if (next && (next.start < curr.end || next.start - curr.end <= 10 /* ms */)) {
       return CollapseFunctions.identical(parent, curr, peek);
     }
   },
 
-  /**
-   * Folds this marker in parent marker if parent marker fully eclipses
-   * the current markers' time.
-   */
-  child: function (parent, curr, peek) {
+  DOMtoDOMJS: function (parent, curr, peek) {
+    // If the next marker is a JavaScript marker, create a new meta parent marker
+    // containing the current marker.
     let next = peek(1);
-    // If this marker is consumed by current parent, collapse
-    if (parent && curr.end <= parent.end) {
-      let finalize = next && next.end > parent.end;
-      return { collapse: true, finalize };
+    if (next && next.name == "Javascript") {
+      return {
+        forceNew: true,
+        toParent: "meta::DOMEvent+JS",
+        withData: {
+          type: curr.type,
+          eventPhase: curr.eventPhase
+        },
+      };
     }
   },
 
-  /**
-   * Turns this marker into a parent marker if the next marker
-   * is fully eclipsed by the current marker.
-   */
-  parent: function (parent, curr, peek) {
-    let next = peek(1);
-    // If the next marker is fully consumed by this marker, make
-    // it a parent (do not collapse, the marker becomes a parent).
-    if (next && curr.end >= next.end) {
-      return { toParent: curr };
+  JStoDOMJS: function (parent, curr, peek) {
+    // If there is a parent marker currently being filled, and it's the one
+    // created from a `DOMEvent` via `collapseDOMIntoDOMJS`, then the current
+    // marker has to go into that one.
+    if (parent && parent.name == "meta::DOMEvent+JS") {
+      return {
+        forceEnd: true,
+        toParent: "meta::DOMEvent+JS",
+        withData: {
+          stack: curr.stack,
+          endStack: curr.endStack
+        },
+      };
     }
   },
 };
 
 /**
  * Mapping of JS marker causes to a friendlier form. Only
  * markers that are considered "from content" should be labeled here.
  */
--- a/browser/devtools/performance/modules/logic/waterfall-utils.js
+++ b/browser/devtools/performance/modules/logic/waterfall-utils.js
@@ -6,126 +6,136 @@
 /**
  * Utility functions for collapsing markers into a waterfall.
  */
 
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/performance/markers", true);
 
 /**
- * Collapses markers into a tree-like structure.
+ * Collapses markers into a tree-like structure. Currently, this only goes
+ * one level deep.
  * @param object markerNode
  * @param array markersList
- * @param ?object blueprint
  */
-function collapseMarkersIntoNode({ markerNode, markersList, blueprint }) {
-  let { getCurrentParentNode, collapseMarker, addParentNode, popParentNode } = createParentNodeFactory(markerNode);
-  blueprint = blueprint || TIMELINE_BLUEPRINT;
+function collapseMarkersIntoNode({ markerNode, markersList }) {
+  let [getOrCreateParentNode, getCurrentParentNode, clearParentNode] = makeParentNodeFactory();
+  let uid = 0;
 
   for (let i = 0, len = markersList.length; i < len; i++) {
     let curr = markersList[i];
 
+    // Make sure all the markers have an assigned number id. This makes it
+    // easier to find them in a waterfall, for example.
+    curr.uid = ++uid;
+
     let parentNode = getCurrentParentNode();
-    let def = blueprint[curr.name];
-    let collapse = def.collapseFunc || (() => null);
+    let blueprint = TIMELINE_BLUEPRINT[curr.name];
+    let collapse = blueprint.collapseFunc || (() => null);
     let peek = distance => markersList[i + distance];
-    let foundParent = false;
+    let collapseInfo = collapse(parentNode, curr, peek);
 
-    let collapseInfo = collapse(parentNode, curr, peek);
     if (collapseInfo) {
-      let { collapse, toParent, finalize } = collapseInfo;
+      let { toParent, withData, forceNew, forceEnd } = collapseInfo;
 
-      // If `toParent` is an object, use it as the next parent marker
-      if (typeof toParent === "object") {
-        addParentNode(toParent);
+      // If the `forceNew` prop is set on the collapse info, then a new parent
+      // marker needs to be created even if there is one already available.
+      if (forceNew) {
+        clearParentNode();
       }
+      // If the `toParent` prop is set on the collapse info, then this marker
+      // can be collapsed into a higher-level parent marker.
+      if (toParent) {
+        let parentNode = getOrCreateParentNode({
+          uid: ++uid,
+          owner: markerNode,
+          name: toParent,
+          start: curr.start,
+          end: curr.end
+        });
 
-      if (collapse) {
-        collapseMarker(curr);
-      }
+        // A parent marker, even when created, will always have at least one
+        // child submarker (the one which caused it to be created).
+        parentNode.submarkers.push(curr);
 
-      // If the marker specifies this parent marker is full,
-      // pop it from the stack.
-      if (finalize) {
-        popParentNode();
+        // Optionally, additional data may be stapled on this parent marker.
+        for (let key in withData) {
+          parentNode[key] = withData[key];
+        }
+      }
+      // If the `forceEnd` prop is set on the collapse info, then the higher-level
+      // parent marker is full and should be finalized.
+      if (forceEnd) {
+        clearParentNode();
       }
     } else {
+      clearParentNode();
       markerNode.submarkers.push(curr);
     }
   }
 }
 
 /**
- * Creates a parent marker, which functions like a regular marker,
+ * Creates an empty parent marker, which functions like a regular marker,
  * but is able to hold additional child markers.
- *
- * The marker is seeded with values from `marker`.
- * @param object marker
+ * @param string name
+ * @param number uid
+ * @param number start [optional]
+ * @param number end [optional]
  * @return object
  */
-function makeParentMarkerNode (marker) {
-  let node = Object.create(null);
-  for (let prop in marker) {
-    node[prop] = marker[prop];
-  }
-  node.submarkers = [];
-  return node;
+function makeEmptyMarkerNode(name, uid, start, end) {
+  return {
+    name: name,
+    uid: uid,
+    start: start,
+    end: end,
+    submarkers: []
+  };
 }
 
 /**
- * Takes a root marker node and creates a hash of functions used
- * to manage the creation and nesting of additional parent markers.
- *
- * @param {object} root
- * @return {object}
+ * Creates a factory for markers containing other markers.
+ * @return array[function]
  */
-function createParentNodeFactory (root) {
-  let parentMarkers = [];
-  let factory = {
+function makeParentNodeFactory() {
+  let marker;
+
+  return [
     /**
-     * Pops the most recent parent node off the stack, finalizing it.
-     * Sets the `end` time based on the most recent child if not defined.
+     * Gets the current parent marker for the given marker name. If it doesn't
+     * exist, it creates it and appends it to another parent marker.
+     * @param object owner
+     * @param string name
+     * @param number start
+     * @param number end
+     * @return object
      */
-    popParentNode: () => {
-      if (parentMarkers.length === 0) {
-        throw new Error("Cannot pop parent markers when none exist.");
+    function getOrCreateParentNode({ owner, name, uid, start, end }) {
+      if (marker && marker.name == name) {
+        marker.end = end;
+        return marker;
+      } else {
+        marker = makeEmptyMarkerNode(name, uid, start, end);
+        owner.submarkers.push(marker);
+        return marker;
       }
-
-      let lastParent = parentMarkers.pop();
-      // If this finished parent marker doesn't have an end time,
-      // so probably a synthesized marker, use the last marker's end time.
-      if (lastParent.end == void 0) {
-        lastParent.end = lastParent.submarkers[lastParent.submarkers.length - 1].end;
-      }
-      return lastParent;
     },
 
     /**
-     * Returns the most recent parent node.
+     * Gets the current marker marker.
+     * @return object
      */
-    getCurrentParentNode: () => parentMarkers.length ? parentMarkers[parentMarkers.length - 1] : null,
-
-    /**
-     * Push a new parent node onto the stack and nest it with the
-     * next most recent parent node, or root if no other parent nodes.
-     */
-    addParentNode: (marker) => {
-      let parentMarker = makeParentMarkerNode(marker);
-      (factory.getCurrentParentNode() || root).submarkers.push(parentMarker);
-      parentMarkers.push(parentMarker);
+    function getCurrentParentNode() {
+      return marker;
     },
 
     /**
-     * Push this marker into the most recent parent node.
+     * Clears the current marker marker.
      */
-    collapseMarker: (marker) => {
-      if (parentMarkers.length === 0) {
-        throw new Error("Cannot collapse marker with no parents.");
-      }
-      factory.getCurrentParentNode().submarkers.push(marker);
+    function clearParentNode() {
+      marker = null;
     }
-  };
-
-  return factory;
+  ];
 }
 
-exports.makeParentMarkerNode = makeParentMarkerNode;
+exports.makeEmptyMarkerNode = makeEmptyMarkerNode;
 exports.collapseMarkersIntoNode = collapseMarkersIntoNode;
--- a/browser/devtools/performance/modules/markers.js
+++ b/browser/devtools/performance/modules/markers.js
@@ -1,15 +1,15 @@
 /* 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 { L10N } = require("devtools/performance/global");
-const { Formatters, CollapseFunctions: collapse } = require("devtools/performance/marker-utils");
+const { Formatters, CollapseFunctions } = require("devtools/performance/marker-utils");
 
 /**
  * A simple schema for mapping markers to the timeline UI. The keys correspond
  * to marker names, while the values are objects with the following format:
  *
  * - group: The row index in the timeline overview graph; multiple markers
  *          can be added on the same row. @see <overview.js/buildGraphImage>
  * - label: The label used in the waterfall to identify the marker. Can be a
@@ -24,23 +24,25 @@ const { Formatters, CollapseFunctions: c
  *              entry in ./browser/themes/shared/devtools/performance.inc.css
  *              https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
  * - collapseFunc: A function determining how markers are collapsed together.
  *                 Invoked with 3 arguments: the current parent marker, the
  *                 current marker and a method for peeking i markers ahead. If
  *                 nothing is returned, the marker is added as a standalone entry
  *                 in the waterfall. Otherwise, an object needs to be returned
  *                 with the following properties:
- *                 - toParent: The marker to be made a new parent. Can use the current
- *                             marker, becoming a parent itself, or make a new marker-esque
- *                             object.
- *                 - collapse: Whether or not this current marker should be nested within
- *                             the current parent.
- *                 - finalize: Whether or not the current parent should be finalized and popped
- *                             off the stack.
+ *                 - toParent: The parent marker name (needs to be an entry in
+ *                             the `TIMELINE_BLUEPRINT` itself).
+ *                 - withData: An object containing some properties to staple
+ *                             on the parent marker.
+ *                 - forceNew: True if a new parent marker needs to be created
+ *                             even though there is one currently available
+ *                             with the same name.
+ *                 - forceEnd: True if the current parent marker is full after
+ *                             this collapse operation and should be finalized.
  * - fields: An optional array of marker properties you wish to display in the
  *           marker details view. For example, a field in the array such as
  *           { property: "aCauseName", label: "Cause" } would render a string
  *           like `Cause: ${marker.aCauseName}` in the marker details view.
  *           Each `field` item may take the following properties:
  *           - property: The property that must exist on the marker to render,
  *                       and the value of the property will be displayed.
  *           - label: The name of the property that should be displayed.
@@ -54,64 +56,69 @@ const { Formatters, CollapseFunctions: c
  * Whenever this is changed, browser_timeline_waterfall-styles.js *must* be
  * updated as well.
  */
 const TIMELINE_BLUEPRINT = {
   /* Group 0 - Reflow and Rendering pipeline */
   "Styles": {
     group: 0,
     colorName: "graphs-purple",
-    collapseFunc: collapse.child,
+    collapseFunc: CollapseFunctions.identical,
     label: L10N.getStr("timeline.label.styles2"),
     fields: Formatters.StylesFields,
   },
   "Reflow": {
     group: 0,
     colorName: "graphs-purple",
-    collapseFunc: collapse.child,
+    collapseFunc: CollapseFunctions.identical,
     label: L10N.getStr("timeline.label.reflow2"),
   },
   "Paint": {
     group: 0,
     colorName: "graphs-green",
-    collapseFunc: collapse.child,
+    collapseFunc: CollapseFunctions.identical,
     label: L10N.getStr("timeline.label.paint"),
   },
 
   /* Group 1 - JS */
   "DOMEvent": {
     group: 1,
     colorName: "graphs-yellow",
-    collapseFunc: collapse.parent,
+    collapseFunc: CollapseFunctions.DOMtoDOMJS,
     label: L10N.getStr("timeline.label.domevent"),
     fields: Formatters.DOMEventFields,
   },
   "Javascript": {
     group: 1,
     colorName: "graphs-yellow",
-    collapseFunc: either(collapse.parent, collapse.child),
+    collapseFunc: either(CollapseFunctions.JStoDOMJS, CollapseFunctions.identical),
     label: Formatters.JSLabel,
     fields: Formatters.JSFields
   },
+  "meta::DOMEvent+JS": {
+    colorName: "graphs-yellow",
+    label: Formatters.DOMJSLabel,
+    fields: Formatters.DOMJSFields,
+  },
   "Parse HTML": {
     group: 1,
     colorName: "graphs-yellow",
-    collapseFunc: either(collapse.parent, collapse.child),
+    collapseFunc: CollapseFunctions.identical,
     label: L10N.getStr("timeline.label.parseHTML"),
   },
   "Parse XML": {
     group: 1,
     colorName: "graphs-yellow",
-    collapseFunc: either(collapse.parent, collapse.child),
+    collapseFunc: CollapseFunctions.identical,
     label: L10N.getStr("timeline.label.parseXML"),
   },
   "GarbageCollection": {
     group: 1,
     colorName: "graphs-red",
-    collapseFunc: either(collapse.parent, collapse.child),
+    collapseFunc: CollapseFunctions.adjacent,
     label: Formatters.GCLabel,
     fields: [
       { property: "causeName", label: "Reason:" },
       { property: "nonincrementalReason", label: "Non-incremental Reason:" }
     ],
   },
 
   /* Group 2 - User Controlled */
@@ -122,17 +129,16 @@ const TIMELINE_BLUEPRINT = {
     fields: [{
       property: "causeName",
       label: L10N.getStr("timeline.markerDetail.consoleTimerName")
     }],
   },
   "TimeStamp": {
     group: 2,
     colorName: "graphs-blue",
-    collapseFunc: collapse.child,
     label: sublabelForProperty(L10N.getStr("timeline.label.timestamp"), "causeName"),
     fields: [{
       property: "causeName",
       label: "Label:"
     }],
   },
 };
 
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -133,8 +133,9 @@ support-files =
 [browser_profiler_tree-view-10.js]
 [browser_timeline-blueprint.js]
 [browser_timeline-filters.js]
 [browser_timeline-waterfall-background.js]
 [browser_timeline-waterfall-generic.js]
 [browser_timeline-waterfall-rerender.js]
 [browser_timeline-waterfall-sidebar.js]
 skip-if = os == 'linux' # Bug 1161817
+[browser_waterfall-collapse.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_waterfall-collapse.js
@@ -0,0 +1,392 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the waterfall collapsing logic works properly.
+ */
+
+function test() {
+  const WaterfallUtils = devtools.require("devtools/performance/waterfall-utils");
+
+  let rootMarkerNode = WaterfallUtils.makeEmptyMarkerNode("(root)");
+
+  WaterfallUtils.collapseMarkersIntoNode({
+    markerNode: rootMarkerNode,
+    markersList: gTestMarkers
+  });
+
+  is(rootMarkerNode.toSource(), gExpectedOutput.toSource(),
+    "The markers didn't collapse properly.");
+
+  finish();
+}
+
+const gTestMarkers = [
+// Test collapsing Style markers
+{
+  start: 1,
+  end: 2,
+  name: "Styles"
+},
+{
+  start: 3,
+  end: 4,
+  name: "Styles"
+},
+// Test collapsing Reflow markers
+{
+  start: 5,
+  end: 6,
+  name: "Reflow"
+},
+{
+  start: 7,
+  end: 8,
+  name: "Reflow"
+},
+// Test collapsing Paint markers
+{
+  start: 9,
+  end: 10,
+  name: "Paint"
+}, {
+  start: 11,
+  end: 12,
+  name: "Paint"
+},
+// Test standalone DOMEvent markers followed by a different marker
+{
+  start: 13,
+  end: 14,
+  name: "DOMEvent",
+  eventPhase: 1,
+  type: "foo1"
+},
+{
+  start: 15,
+  end: 16,
+  name: "TimeStamp"
+},
+// Test a DOMEvent marker followed by a Javascript marker.
+{
+  start: 17,
+  end: 18,
+  name: "DOMEvent",
+  eventPhase: 2,
+  type: "foo2"
+}, {
+  start: 19,
+  end: 20,
+  name: "Javascript",
+  stack: 1,
+  endStack: 2
+},
+// Test another DOMEvent marker followed by a Javascript marker.
+{
+  start: 21,
+  end: 22,
+  name: "DOMEvent",
+  eventPhase: 3,
+  type: "foo3"
+}, {
+  start: 23,
+  end: 24,
+  name: "Javascript",
+  stack: 3,
+  endStack: 4
+},
+// Test a DOMEvent marker followed by multiple Javascript markers.
+{
+  start: 25,
+  end: 26,
+  name: "DOMEvent",
+  eventPhase: 4,
+  type: "foo4"
+}, {
+  start: 27,
+  end: 28,
+  name: "Javascript",
+  stack: 5,
+  endStack: 6
+}, {
+  start: 29,
+  end: 30,
+  name: "Javascript",
+  stack: 7,
+  endStack: 8
+}, {
+  start: 31,
+  end: 32,
+  name: "Javascript",
+  stack: 9,
+  endStack: 10
+},
+// Test multiple DOMEvent markers followed by multiple Javascript markers.
+{
+  start: 33,
+  end: 34,
+  name: "DOMEvent",
+  eventPhase: 5,
+  type: "foo5"
+}, {
+  start: 35,
+  end: 36,
+  name: "DOMEvent",
+  eventPhase: 6,
+  type: "foo6"
+}, {
+  start: 37,
+  end: 38,
+  name: "DOMEvent",
+  eventPhase: 7,
+  type: "foo6"
+}, {
+  start: 39,
+  end: 40,
+  name: "Javascript",
+  stack: 11,
+  endStack: 12
+}, {
+  start: 41,
+  end: 42,
+  name: "Javascript",
+  stack: 13,
+  endStack: 14
+}, {
+  start: 43,
+  end: 44,
+  name: "Javascript",
+  stack: 15,
+  endStack: 16
+},
+// Test a lonely marker at the end.
+{
+  start: 45,
+  end: 46,
+  name: "GarbageCollection"
+}
+];
+
+const gExpectedOutput = {
+  name: "(root)",
+  uid: (void 0),
+  start: (void 0),
+  end: (void 0),
+  submarkers: [{
+    name: "Styles",
+    uid: 2,
+    start: 1,
+    end: 4,
+    submarkers: [{
+      start: 1,
+      end: 2,
+      name: "Styles",
+      uid: 1
+    }, {
+      start: 3,
+      end: 4,
+      name: "Styles",
+      uid: 3
+    }]
+  }, {
+    name: "Reflow",
+    uid: 6,
+    start: 5,
+    end: 8,
+    submarkers: [{
+      start: 5,
+      end: 6,
+      name: "Reflow",
+      uid: 5
+    }, {
+      start: 7,
+      end: 8,
+      name: "Reflow",
+      uid: 7
+    }]
+  }, {
+    name: "Paint",
+    uid: 10,
+    start: 9,
+    end: 12,
+    submarkers: [{
+      start: 9,
+      end: 10,
+      name: "Paint",
+      uid: 9
+    }, {
+      start: 11,
+      end: 12,
+      name: "Paint",
+      uid: 11
+    }]
+  }, {
+    start: 13,
+    end: 14,
+    name: "DOMEvent",
+    eventPhase: 1,
+    type: "foo1",
+    uid: 13
+  }, {
+    start: 15,
+    end: 16,
+    name: "TimeStamp",
+    uid: 14
+  }, {
+    name: "meta::DOMEvent+JS",
+    uid: 16,
+    start: 17,
+    end: 20,
+    submarkers: [{
+      start: 17,
+      end: 18,
+      name: "DOMEvent",
+      eventPhase: 2,
+      type: "foo2",
+      uid: 15
+    }, {
+      start: 19,
+      end: 20,
+      name: "Javascript",
+      stack: 1,
+      endStack: 2,
+      uid: 17
+    }],
+    type: "foo2",
+    eventPhase: 2,
+    stack: 1,
+    endStack: 2
+  }, {
+    name: "meta::DOMEvent+JS",
+    uid: 20,
+    start: 21,
+    end: 24,
+    submarkers: [{
+      start: 21,
+      end: 22,
+      name: "DOMEvent",
+      eventPhase: 3,
+      type: "foo3",
+      uid: 19
+    }, {
+      start: 23,
+      end: 24,
+      name: "Javascript",
+      stack: 3,
+      endStack: 4,
+      uid: 21
+    }],
+    type: "foo3",
+    eventPhase: 3,
+    stack: 3,
+    endStack: 4
+  }, {
+    name: "meta::DOMEvent+JS",
+    uid: 24,
+    start: 25,
+    end: 28,
+    submarkers: [{
+      start: 25,
+      end: 26,
+      name: "DOMEvent",
+      eventPhase: 4,
+      type: "foo4",
+      uid: 23
+    }, {
+      start: 27,
+      end: 28,
+      name: "Javascript",
+      stack: 5,
+      endStack: 6,
+      uid: 25
+    }],
+    type: "foo4",
+    eventPhase: 4,
+    stack: 5,
+    endStack: 6
+  }, {
+    name: "Javascript",
+    uid: 28,
+    start: 29,
+    end: 32,
+    submarkers: [{
+      start: 29,
+      end: 30,
+      name: "Javascript",
+      stack: 7,
+      endStack: 8,
+      uid: 27
+    }, {
+      start: 31,
+      end: 32,
+      name: "Javascript",
+      stack: 9,
+      endStack: 10,
+      uid: 29
+    }]
+  }, {
+    start: 33,
+    end: 34,
+    name: "DOMEvent",
+    eventPhase: 5,
+    type: "foo5",
+    uid: 31
+  }, {
+    start: 35,
+    end: 36,
+    name: "DOMEvent",
+    eventPhase: 6,
+    type: "foo6",
+    uid: 32
+  }, {
+    name: "meta::DOMEvent+JS",
+    uid: 34,
+    start: 37,
+    end: 40,
+    submarkers: [{
+      start: 37,
+      end: 38,
+      name: "DOMEvent",
+      eventPhase: 7,
+      type: "foo6",
+      uid: 33
+    }, {
+      start: 39,
+      end: 40,
+      name: "Javascript",
+      stack: 11,
+      endStack: 12,
+      uid: 35
+    }],
+    type: "foo6",
+    eventPhase: 7,
+    stack: 11,
+    endStack: 12
+  }, {
+    name: "Javascript",
+    uid: 38,
+    start: 41,
+    end: 44,
+    submarkers: [{
+      start: 41,
+      end: 42,
+      name: "Javascript",
+      stack: 13,
+      endStack: 14,
+      uid: 37
+    }, {
+      start: 43,
+      end: 44,
+      name: "Javascript",
+      stack: 15,
+      endStack: 16,
+      uid: 39
+    }]
+  }, {
+    start: 45,
+    end: 46,
+    name: "GarbageCollection",
+    uid: 41
+  }]
+};
+
deleted file mode 100644
--- a/browser/devtools/performance/test/unit/test_waterfall-utils-collapse-01.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the waterfall collapsing logic works properly.
- */
-
-function test() {
-  const WaterfallUtils = devtools.require("devtools/performance/waterfall-utils");
-
-  let rootMarkerNode = WaterfallUtils.makeParentMarkerNode({ name: "(root)" });
-
-  WaterfallUtils.collapseMarkersIntoNode({
-    markerNode: rootMarkerNode,
-    markersList: gTestMarkers
-  });
-
-  function compare (marker, expected) {
-    for (let prop in expected) {
-      if (prop === "submarkers") {
-        for (let i = 0; i < expected.submarkers.length; i++) {
-          compare(marker.submarkers[i], expected.submarkers[i]);
-        }
-      } else if (prop !== "uid") {
-        is(marker[prop], expected[prop], `${expected.name} matches ${prop}`);
-      }
-    }
-  }
-
-  compare(rootMarkerNode, gExpectedOutput);
-  finish();
-}
-
-const gTestMarkers = [
-  { start: 1, end: 18, name: "DOMEvent" },
-    // Test that JS markers can fold in DOM events and have marker children
-    { start: 2, end: 16, name: "Javascript" },
-      // Test all these markers can be children
-      { start: 3, end: 4, name: "Paint" },
-      { start: 5, end: 6, name: "Reflow" },
-      { start: 7, end: 8, name: "Styles" },
-      { start: 9, end: 9, name: "TimeStamp" },
-      { start: 10, end: 11, name: "Parse HTML" },
-      { start: 12, end: 13, name: "Parse XML" },
-      { start: 14, end: 15, name: "GarbageCollection" },
-  // Test that JS markers can be parents without being a child of DOM events
-  { start: 25, end: 30, name: "JavaScript" },
-    { start: 26, end: 27, name: "Paint" },
-];
-
-const gExpectedOutput = {
-  name: "(root)", submarkers: [
-    { start: 1, end: 18, name: "DOMEvent", submarkers: [
-      { start: 2, end: 16, name: "Javascript", submarkers: [
-        { start: 3, end: 4, name: "Paint" },
-        { start: 5, end: 6, name: "Reflow" },
-        { start: 7, end: 8, name: "Styles" },
-        { start: 9, end: 9, name: "TimeStamp" },
-        { start: 10, end: 11, name: "Parse HTML" },
-        { start: 12, end: 13, name: "Parse XML" },
-        { start: 14, end: 15, name: "GarbageCollection" },
-      ]}
-    ]},
-    { start: 25, end: 30, name: "JavaScript", submarkers: [
-      { start: 26, end: 27, name: "Paint" },
-    ]}
-]};
deleted file mode 100644
--- a/browser/devtools/performance/test/unit/test_waterfall-utils-collapse-02.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the waterfall collapsing logic works properly for console.time/console.timeEnd
- * markers, as they should ignore any sort of collapsing.
- */
-
-function test() {
-  const WaterfallUtils = devtools.require("devtools/performance/waterfall-utils");
-
-  let rootMarkerNode = WaterfallUtils.makeParentMarkerNode({ name: "(root)" });
-
-  WaterfallUtils.collapseMarkersIntoNode({
-    markerNode: rootMarkerNode,
-    markersList: gTestMarkers
-  });
-
-  function compare (marker, expected) {
-    for (let prop in expected) {
-      if (prop === "submarkers") {
-        for (let i = 0; i < expected.submarkers.length; i++) {
-          compare(marker.submarkers[i], expected.submarkers[i]);
-        }
-      } else if (prop !== "uid") {
-        is(marker[prop], expected[prop], `${expected.name} matches ${prop}`);
-      }
-    }
-  }
-
-  compare(rootMarkerNode, gExpectedOutput);
-  finish();
-}
-
-const gTestMarkers = [
-  { start: 2, end: 9, name: "Javascript" },
-    { start: 3, end: 4, name: "Paint" },
-  // Time range starting in nest, ending outside
-  { start: 5, end: 12, name: "ConsoleTime", causeName: "1" },
-
-  // Time range starting outside of nest, ending inside
-  { start: 15, end: 21, name: "ConsoleTime", causeName: "2" },
-  { start: 18, end: 22, name: "Javascript" },
-    { start: 19, end: 20, name: "Paint" },
-
-  // Time range completely eclipsing nest
-  { start: 30, end: 40, name: "ConsoleTime", causeName: "3" },
-  { start: 34, end: 39, name: "Javascript" },
-    { start: 35, end: 36, name: "Paint" },
-
-  // Time range completely eclipsed by nest
-  { start: 50, end: 60, name: "Javascript" },
-  { start: 54, end: 59, name: "ConsoleTime", causeName: "4" },
-    { start: 56, end: 57, name: "Paint" },
-];
-
-const gExpectedOutput = {
-  name: "(root)", submarkers: [
-    { start: 2, end: 9, name: "Javascript", submarkers: [
-      { start: 3, end: 4, name: "Paint" }
-    ]},
-    { start: 5, end: 12, name: "ConsoleTime", causeName: "1" },
-
-    { start: 15, end: 21, name: "ConsoleTime", causeName: "2" },
-    { start: 18, end: 22, name: "Javascript", submarkers: [
-      { start: 19, end: 20, name: "Paint" }
-    ]},
-    
-    { start: 30, end: 40, name: "ConsoleTime", causeName: "3" },
-    { start: 34, end: 39, name: "Javascript", submarkers: [
-      { start: 35, end: 36, name: "Paint" },
-    ]},
-
-    { start: 50, end: 60, name: "Javascript", submarkers: [
-      { start: 56, end: 57, name: "Paint" },
-    ]},
-    { start: 54, end: 59, name: "ConsoleTime", causeName: "4" },
-]};
--- a/browser/devtools/performance/test/unit/xpcshell.ini
+++ b/browser/devtools/performance/test/unit/xpcshell.ini
@@ -12,10 +12,8 @@ skip-if = toolkit == 'android' || toolki
 [test_tree-model-02.js]
 [test_tree-model-03.js]
 [test_tree-model-04.js]
 [test_tree-model-05.js]
 [test_tree-model-06.js]
 [test_tree-model-07.js]
 [test_tree-model-08.js]
 [test_tree-model-09.js]
-[test_waterfall-utils-collapse-01.js]
-[test_waterfall-utils-collapse-02.js]
--- a/browser/devtools/performance/views/details-waterfall.js
+++ b/browser/devtools/performance/views/details-waterfall.js
@@ -85,17 +85,17 @@ let WaterfallView = Heritage.extend(Deta
    */
   _onMarkerSelected: function (event, marker) {
     let recording = PerformanceController.getCurrentRecording();
     let frames = recording.getFrames();
 
     if (event === "selected") {
       this.details.render({ toolbox: gToolbox, marker, frames });
       this.details.hidden = false;
-      this._lastSelected = marker;
+      this._lastSelected = marker.uid;
     }
     if (event === "unselected") {
       this.details.empty();
     }
   },
 
   /**
    * Called when the marker details view is resized.
@@ -127,17 +127,17 @@ let WaterfallView = Heritage.extend(Deta
    * populate the waterfall tree.
    */
   _prepareWaterfallTree: function(markers) {
     let cached = this._cache.get(markers);
     if (cached) {
       return cached;
     }
 
-    let rootMarkerNode = WaterfallUtils.makeParentMarkerNode({ name: "(root)" });
+    let rootMarkerNode = WaterfallUtils.makeEmptyMarkerNode("(root)");
 
     WaterfallUtils.collapseMarkersIntoNode({
       markerNode: rootMarkerNode,
       markersList: markers
     });
 
     this._cache.set(markers, rootMarkerNode);
     return rootMarkerNode;
@@ -170,17 +170,17 @@ let WaterfallView = Heritage.extend(Deta
     root.attachTo(this.breakdownContainer);
 
     this.headerContainer.innerHTML = "";
     header.attachTo(this.headerContainer);
 
     // If an item was previously selected in this view, attempt to
     // re-select it by traversing the newly created tree.
     if (this._lastSelected) {
-      let item = root.find(i => i.marker === this._lastSelected);
+      let item = root.find(i => i.marker.uid == this._lastSelected);
       if (item) {
         item.focus();
       }
     }
   },
 
   toString: () => "[object WaterfallView]"
 });