Bug 1081245 - Make the call tree text select and copy-able. r=nfitzgerald
authorHernan Rodriguez Colmeiro <colmeiro@gmail.com>
Fri, 30 Oct 2015 02:57:00 -0300
changeset 313310 6b31012ac6d26a19dc516efc08e733b21118179e
parent 313309 650a83a0b67527ba0b32b6bbbb064d28d4a31af4
child 313311 1cdb9a03dac2ca2ed41ac2121253d9702c701ac2
push id1079
push userjlund@mozilla.com
push dateFri, 15 Apr 2016 21:02:33 +0000
treeherdermozilla-release@575fbf6786d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnfitzgerald
bugs1081245
milestone45.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 1081245 - Make the call tree text select and copy-able. r=nfitzgerald
devtools/client/performance/modules/widgets/tree-view.js
devtools/client/performance/test/browser_perf-categories-js-calltree.js
devtools/client/performance/test/browser_profiler_tree-view-01.js
devtools/client/performance/test/browser_profiler_tree-view-02.js
devtools/client/performance/test/browser_profiler_tree-view-03.js
devtools/client/performance/test/browser_profiler_tree-view-08.js
devtools/client/performance/test/browser_profiler_tree-view-10.js
devtools/client/performance/test/browser_profiler_tree-view-11.js
devtools/client/shared/widgets/AbstractTreeItem.jsm
devtools/client/themes/performance.css
--- a/devtools/client/performance/modules/widgets/tree-view.js
+++ b/devtools/client/performance/modules/widgets/tree-view.js
@@ -230,17 +230,18 @@ CallView.prototype = Heritage.extend(Abs
    * Functions creating each cell in this call view.
    * Invoked by `_displaySelf`.
    */
   _createCell: function (doc, value, type) {
     let cell = doc.createElement("description");
     cell.className = "plain call-tree-cell";
     cell.setAttribute("type", type);
     cell.setAttribute("crop", "end");
-    cell.setAttribute("value", value);
+    // Add a tabulation to the cell text in case it's is selected and copied.
+    cell.textContent = value + "\t";
     return cell;
   },
 
   _createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) {
     let cell = doc.createElement("hbox");
     cell.className = "call-tree-cell";
     cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
     cell.setAttribute("type", "function");
@@ -256,76 +257,87 @@ CallView.prototype = Heritage.extend(Abs
 
     // Don't render a name label node if there's no function name. A different
     // location label node will be rendered instead.
     if (frameName) {
       let nameNode = doc.createElement("description");
       nameNode.className = "plain call-tree-name";
       nameNode.setAttribute("flex", "1");
       nameNode.setAttribute("crop", "end");
-      nameNode.setAttribute("value", frameName);
+      nameNode.textContent = frameName;
       cell.appendChild(nameNode);
     }
 
     // Don't render detailed labels for meta category frames
     if (!frameInfo.isMetaCategory) {
       this._appendFunctionDetailsCells(doc, cell, frameInfo);
     }
 
     // Don't render an expando-arrow for leaf nodes.
     let hasDescendants = Object.keys(this.frame.calls).length > 0;
     if (!hasDescendants) {
       arrowNode.setAttribute("invisible", "");
     }
 
+    // Add a line break to the last description of the row in case it's selected
+    // and copied.
+    let lastDescription = cell.querySelector('description:last-of-type');
+    lastDescription.textContent = lastDescription.textContent + "\n";
+
+    // Add spaces as frameLevel indicators in case the row is selected and
+    // copied. These spaces won't be displayed in the cell content.
+    let firstDescription = cell.querySelector('description:first-of-type');
+    let levelIndicator = frameLevel > 0 ? " ".repeat(frameLevel) : "";
+    firstDescription.textContent = levelIndicator + firstDescription.textContent;
+
     return cell;
   },
 
   _appendFunctionDetailsCells: function(doc, cell, frameInfo) {
     if (frameInfo.fileName) {
       let urlNode = doc.createElement("description");
       urlNode.className = "plain call-tree-url";
       urlNode.setAttribute("flex", "1");
       urlNode.setAttribute("crop", "end");
-      urlNode.setAttribute("value", frameInfo.fileName);
+      urlNode.textContent = frameInfo.fileName;
       urlNode.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + frameInfo.url);
       urlNode.addEventListener("mousedown", this._onUrlClick);
       cell.appendChild(urlNode);
     }
 
     if (frameInfo.line) {
       let lineNode = doc.createElement("description");
       lineNode.className = "plain call-tree-line";
-      lineNode.setAttribute("value", ":" + frameInfo.line);
+      lineNode.textContent = ":" + frameInfo.line;
       cell.appendChild(lineNode);
     }
 
     if (frameInfo.column) {
       let columnNode = doc.createElement("description");
       columnNode.className = "plain call-tree-column";
-      columnNode.setAttribute("value", ":" + frameInfo.column);
+      columnNode.textContent = ":" + frameInfo.column;
       cell.appendChild(columnNode);
     }
 
     if (frameInfo.host) {
       let hostNode = doc.createElement("description");
       hostNode.className = "plain call-tree-host";
-      hostNode.setAttribute("value", frameInfo.host);
+      hostNode.textContent = frameInfo.host;
       cell.appendChild(hostNode);
     }
 
     let spacerNode = doc.createElement("spacer");
     spacerNode.setAttribute("flex", "10000");
     cell.appendChild(spacerNode);
 
     if (frameInfo.categoryData.label) {
       let categoryNode = doc.createElement("description");
       categoryNode.className = "plain call-tree-category";
       categoryNode.style.color = frameInfo.categoryData.color;
-      categoryNode.setAttribute("value", frameInfo.categoryData.label);
+      categoryNode.textContent = frameInfo.categoryData.label;
       cell.appendChild(categoryNode);
     }
   },
 
   /**
    * Gets the data displayed about this tree item, based on the FrameNode
    * model associated with this view.
    *
--- a/devtools/client/performance/test/browser_perf-categories-js-calltree.js
+++ b/devtools/client/performance/test/browser_perf-categories-js-calltree.js
@@ -2,37 +2,46 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the categories are shown in the js call tree when platform data
  * is enabled.
  */
 function* spawnTest() {
   let { panel } = yield initPerformance(SIMPLE_URL);
-  let { EVENTS, $, DetailsView, JsCallTreeView } = panel.panelWin;
+  let { EVENTS, $, $$, DetailsView, JsCallTreeView } = panel.panelWin;
 
   // Enable platform data to show the categories.
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
 
   yield startRecording(panel);
   yield busyWait(100);
 
   let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
   yield stopRecording(panel);
   yield DetailsView.selectView("js-calltree");
   yield rendered;
 
   is($(".call-tree-cells-container").hasAttribute("categories-hidden"), false,
     "The call tree cells container should show the categories now.");
-  ok($(".call-tree-category[value=Gecko]"),
-    "A category node with the label `Gecko` is displayed in the tree.");
+  ok(geckoCategoryPresent($$),
+    "A category node with the text `Gecko` is displayed in the tree.");
 
   // Disable platform data to show the categories.
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
 
   is($(".call-tree-cells-container").getAttribute("categories-hidden"), "",
     "The call tree cells container should hide the categories now.");
-  ok(!$(".call-tree-category[value=Gecko]"),
-    "A category node with the label `Gecko` doesn't exist in the tree anymore.");
+  ok(!geckoCategoryPresent($$),
+    "A category node with the text `Gecko` doesn't exist in the tree anymore.");
 
   yield teardown(panel);
   finish();
 }
+
+function geckoCategoryPresent($$) {
+  for (let elem of $$('.call-tree-category')) {
+    if (elem.textContent.trim() == 'Gecko') {
+      return true
+    }
+  }
+  return false
+}
--- a/devtools/client/performance/test/browser_profiler_tree-view-01.js
+++ b/devtools/client/performance/test/browser_profiler_tree-view-01.js
@@ -26,37 +26,37 @@ function test() {
 
   is(container.childNodes[0].childNodes.length, 6,
     "The root node in the tree has the correct number of children.");
   is(container.childNodes[0].querySelectorAll(".call-tree-cell").length, 6,
     "The root node in the tree has only 6 'call-tree-cell' children.");
 
   is(container.childNodes[0].childNodes[0].getAttribute("type"), "duration",
     "The root node in the tree has a duration cell.");
-  is(container.childNodes[0].childNodes[0].getAttribute("value"), "20 ms",
+  is(container.childNodes[0].childNodes[0].textContent.trim(), "20 ms",
     "The root node in the tree has the correct duration cell value.");
 
   is(container.childNodes[0].childNodes[1].getAttribute("type"), "percentage",
     "The root node in the tree has a percentage cell.");
-  is(container.childNodes[0].childNodes[1].getAttribute("value"), "100%",
+  is(container.childNodes[0].childNodes[1].textContent.trim(), "100%",
     "The root node in the tree has the correct percentage cell value.");
 
   is(container.childNodes[0].childNodes[2].getAttribute("type"), "self-duration",
     "The root node in the tree has a self-duration cell.");
-  is(container.childNodes[0].childNodes[2].getAttribute("value"), "0 ms",
+  is(container.childNodes[0].childNodes[2].textContent.trim(), "0 ms",
     "The root node in the tree has the correct self-duration cell value.");
 
   is(container.childNodes[0].childNodes[3].getAttribute("type"), "self-percentage",
     "The root node in the tree has a self-percentage cell.");
-  is(container.childNodes[0].childNodes[3].getAttribute("value"), "0%",
+  is(container.childNodes[0].childNodes[3].textContent.trim(), "0%",
     "The root node in the tree has the correct self-percentage cell value.");
 
   is(container.childNodes[0].childNodes[4].getAttribute("type"), "samples",
     "The root node in the tree has an samples cell.");
-  is(container.childNodes[0].childNodes[4].getAttribute("value"), "0",
+  is(container.childNodes[0].childNodes[4].textContent.trim(), "0",
     "The root node in the tree has the correct samples cell value.");
 
   is(container.childNodes[0].childNodes[5].getAttribute("type"), "function",
     "The root node in the tree has a function cell.");
   is(container.childNodes[0].childNodes[5].style.MozMarginStart, "0px",
     "The root node in the tree has the correct indentation.");
 
   finish();
--- a/devtools/client/performance/test/browser_profiler_tree-view-02.js
+++ b/devtools/client/performance/test/browser_profiler_tree-view-02.js
@@ -28,23 +28,23 @@ function test() {
   let $$perc = i => container.querySelectorAll(".call-tree-cell[type=percentage]")[i];
   let $$sampl = i => container.querySelectorAll(".call-tree-cell[type=samples]")[i];
 
   is(container.childNodes.length, 1,
     "The container node should have one child available.");
   is(container.childNodes[0].className, "call-tree-item",
     "The root node in the tree has the correct class name.");
 
-  is($$dur(0).getAttribute("value"), "20 ms",
+  is($$dur(0).textContent.trim(), "20 ms",
     "The root's duration cell displays the correct value.");
-  is($$perc(0).getAttribute("value"), "100%",
+  is($$perc(0).textContent.trim(), "100%",
     "The root's percentage cell displays the correct value.");
-  is($$sampl(0).getAttribute("value"), "0",
+  is($$sampl(0).textContent.trim(), "0",
     "The root's samples cell displays the correct value.");
-  is($$fun(".call-tree-name")[0].getAttribute("value"), "(root)",
+  is($$fun(".call-tree-name")[0].textContent.trim(), "(root)",
     "The root's function cell displays the correct name.");
   is($$fun(".call-tree-url")[0], null,
     "The root's function cell displays no url.");
   is($$fun(".call-tree-line")[0], null,
     "The root's function cell displays no line.");
   is($$fun(".call-tree-host")[0], null,
     "The root's function cell displays the correct host.");
   is($$fun(".call-tree-category")[0], null,
@@ -54,81 +54,81 @@ function test() {
 
   is(container.childNodes.length, 2,
     "The container node should have two children available.");
   is(container.childNodes[0].className, "call-tree-item",
     "The root node in the tree has the correct class name.");
   is(container.childNodes[1].className, "call-tree-item",
     "The .A node in the tree has the correct class name.");
 
-  is($$dur(1).getAttribute("value"), "20 ms",
+  is($$dur(1).textContent.trim(), "20 ms",
     "The .A node's duration cell displays the correct value.");
-  is($$perc(1).getAttribute("value"), "100%",
+  is($$perc(1).textContent.trim(), "100%",
     "The .A node's percentage cell displays the correct value.");
-  is($$sampl(1).getAttribute("value"), "0",
+  is($$sampl(1).textContent.trim(), "0",
     "The .A node's samples cell displays the correct value.");
-  is($fun(".call-tree-name", $$(".call-tree-item")[1]).getAttribute("value"), "A",
+  is($fun(".call-tree-name", $$(".call-tree-item")[1]).textContent.trim(), "A",
     "The .A node's function cell displays the correct name.");
-  is($fun(".call-tree-url", $$(".call-tree-item")[1]).getAttribute("value"), "baz",
+  is($fun(".call-tree-url", $$(".call-tree-item")[1]).textContent.trim(), "baz",
     "The .A node's function cell displays the correct url.");
   ok($fun(".call-tree-url", $$(".call-tree-item")[1]).getAttribute("tooltiptext").includes("http://foo/bar/baz"),
     "The .A node's function cell displays the correct url tooltiptext.");
-  is($fun(".call-tree-line", $$(".call-tree-item")[1]).getAttribute("value"), ":12",
+  is($fun(".call-tree-line", $$(".call-tree-item")[1]).textContent.trim(), ":12",
     "The .A node's function cell displays the correct line.");
-  is($fun(".call-tree-host", $$(".call-tree-item")[1]).getAttribute("value"), "foo",
+  is($fun(".call-tree-host", $$(".call-tree-item")[1]).textContent.trim(), "foo",
     "The .A node's function cell displays the correct host.");
-  is($fun(".call-tree-category", $$(".call-tree-item")[1]).getAttribute("value"), "Gecko",
+  is($fun(".call-tree-category", $$(".call-tree-item")[1]).textContent.trim(), "Gecko",
     "The .A node's function cell displays the correct category.");
 
   let A = treeRoot.getChild();
   A.expand();
 
   is(container.childNodes.length, 4,
     "The container node should have four children available.");
   is(container.childNodes[2].className, "call-tree-item",
     "The .B node in the tree has the correct class name.");
   is(container.childNodes[3].className, "call-tree-item",
     "The .E node in the tree has the correct class name.");
 
-  is($$dur(2).getAttribute("value"), "15 ms",
+  is($$dur(2).textContent.trim(), "15 ms",
     "The .A.B node's duration cell displays the correct value.");
-  is($$perc(2).getAttribute("value"), "75%",
+  is($$perc(2).textContent.trim(), "75%",
     "The .A.B node's percentage cell displays the correct value.");
-  is($$sampl(2).getAttribute("value"), "0",
+  is($$sampl(2).textContent.trim(), "0",
     "The .A.B node's samples cell displays the correct value.");
-  is($fun(".call-tree-name", $$(".call-tree-item")[2]).getAttribute("value"), "B",
+  is($fun(".call-tree-name", $$(".call-tree-item")[2]).textContent.trim(), "B",
     "The .A.B node's function cell displays the correct name.");
-  is($fun(".call-tree-url", $$(".call-tree-item")[2]).getAttribute("value"), "baz",
+  is($fun(".call-tree-url", $$(".call-tree-item")[2]).textContent.trim(), "baz",
     "The .A.B node's function cell displays the correct url.");
   ok($fun(".call-tree-url", $$(".call-tree-item")[2]).getAttribute("tooltiptext").includes("http://foo/bar/baz"),
     "The .A.B node's function cell displays the correct url tooltiptext.");
-  is($fun(".call-tree-line", $$(".call-tree-item")[2]).getAttribute("value"), ":34",
+  is($fun(".call-tree-line", $$(".call-tree-item")[2]).textContent.trim(), ":34",
     "The .A.B node's function cell displays the correct line.");
-  is($fun(".call-tree-host", $$(".call-tree-item")[2]).getAttribute("value"), "foo",
+  is($fun(".call-tree-host", $$(".call-tree-item")[2]).textContent.trim(), "foo",
     "The .A.B node's function cell displays the correct host.");
-  is($fun(".call-tree-category", $$(".call-tree-item")[2]).getAttribute("value"), "Styles",
+  is($fun(".call-tree-category", $$(".call-tree-item")[2]).textContent.trim(), "Styles",
     "The .A.B node's function cell displays the correct category.");
 
-  is($$dur(3).getAttribute("value"), "5 ms",
+  is($$dur(3).textContent.trim(), "5 ms",
     "The .A.E node's duration cell displays the correct value.");
-  is($$perc(3).getAttribute("value"), "25%",
+  is($$perc(3).textContent.trim(), "25%",
     "The .A.E node's percentage cell displays the correct value.");
-  is($$sampl(3).getAttribute("value"), "0",
+  is($$sampl(3).textContent.trim(), "0",
     "The .A.E node's samples cell displays the correct value.");
-  is($fun(".call-tree-name", $$(".call-tree-item")[3]).getAttribute("value"), "E",
+  is($fun(".call-tree-name", $$(".call-tree-item")[3]).textContent.trim(), "E",
     "The .A.E node's function cell displays the correct name.");
-  is($fun(".call-tree-url", $$(".call-tree-item")[3]).getAttribute("value"), "baz",
+  is($fun(".call-tree-url", $$(".call-tree-item")[3]).textContent.trim(), "baz",
     "The .A.E node's function cell displays the correct url.");
   ok($fun(".call-tree-url", $$(".call-tree-item")[3]).getAttribute("tooltiptext").includes("http://foo/bar/baz"),
     "The .A.E node's function cell displays the correct url tooltiptext.");
-  is($fun(".call-tree-line", $$(".call-tree-item")[3]).getAttribute("value"), ":90",
+  is($fun(".call-tree-line", $$(".call-tree-item")[3]).textContent.trim(), ":90",
     "The .A.E node's function cell displays the correct line.");
-  is($fun(".call-tree-host", $$(".call-tree-item")[3]).getAttribute("value"), "foo",
+  is($fun(".call-tree-host", $$(".call-tree-item")[3]).textContent.trim(), "foo",
     "The .A.E node's function cell displays the correct host.");
-  is($fun(".call-tree-category", $$(".call-tree-item")[3]).getAttribute("value"), "GC",
+  is($fun(".call-tree-category", $$(".call-tree-item")[3]).textContent.trim(), "GC",
     "The .A.E node's function cell displays the correct category.");
 
   finish();
 }
 
 var gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
--- a/devtools/client/performance/test/browser_profiler_tree-view-03.js
+++ b/devtools/client/performance/test/browser_profiler_tree-view-03.js
@@ -37,44 +37,44 @@ function test() {
     "The .A.B.D node's function cell has the correct indentation.");
   is($$fun(4).style.MozMarginStart, "48px",
     "The .A.B.C node's function cell has the correct indentation.");
   is($$fun(5).style.MozMarginStart, "32px",
     "The .A.E node's function cell has the correct indentation.");
   is($$fun(6).style.MozMarginStart, "48px",
     "The .A.E.F node's function cell has the correct indentation.");
 
-  is($$name(0).getAttribute("value"), "(root)",
+  is($$name(0).textContent.trim(), "(root)",
     "The root node's function cell displays the correct name.");
-  is($$name(1).getAttribute("value"), "A",
+  is($$name(1).textContent.trim(), "A",
     "The .A node's function cell displays the correct name.");
-  is($$name(2).getAttribute("value"), "B",
+  is($$name(2).textContent.trim(), "B",
     "The .A.B node's function cell displays the correct name.");
-  is($$name(3).getAttribute("value"), "D",
+  is($$name(3).textContent.trim(), "D",
     "The .A.B.D node's function cell displays the correct name.");
-  is($$name(4).getAttribute("value"), "C",
+  is($$name(4).textContent.trim(), "C",
     "The .A.B.C node's function cell displays the correct name.");
-  is($$name(5).getAttribute("value"), "E",
+  is($$name(5).textContent.trim(), "E",
     "The .A.E node's function cell displays the correct name.");
-  is($$name(6).getAttribute("value"), "F",
+  is($$name(6).textContent.trim(), "F",
     "The .A.E.F node's function cell displays the correct name.");
 
-  is($$duration(0).getAttribute("value"), "20 ms",
+  is($$duration(0).textContent.trim(), "20 ms",
     "The root node's function cell displays the correct duration.");
-  is($$duration(1).getAttribute("value"), "20 ms",
+  is($$duration(1).textContent.trim(), "20 ms",
     "The .A node's function cell displays the correct duration.");
-  is($$duration(2).getAttribute("value"), "15 ms",
+  is($$duration(2).textContent.trim(), "15 ms",
     "The .A.B node's function cell displays the correct duration.");
-  is($$duration(3).getAttribute("value"), "10 ms",
+  is($$duration(3).textContent.trim(), "10 ms",
     "The .A.B.D node's function cell displays the correct duration.");
-  is($$duration(4).getAttribute("value"), "5 ms",
+  is($$duration(4).textContent.trim(), "5 ms",
     "The .A.B.C node's function cell displays the correct duration.");
-  is($$duration(5).getAttribute("value"), "5 ms",
+  is($$duration(5).textContent.trim(), "5 ms",
     "The .A.E node's function cell displays the correct duration.");
-  is($$duration(6).getAttribute("value"), "5 ms",
+  is($$duration(6).textContent.trim(), "5 ms",
     "The .A.E.F node's function cell displays the correct duration.");
 
   finish();
 }
 
 var gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
@@ -103,9 +103,8 @@ var gThread = synthesizeProfileForTest([
   time: 5 + 1 + 2 + 7,
   frames: [
     { category: 8,   location: "(root)" },
     { category: 8,   location: "A (http://foo/bar/baz:12)" },
     { category: 128, location: "E (http://foo/bar/baz:90)" },
     { category: 256, location: "F (http://foo/bar/baz:99)" }
   ]
 }]);
-
--- a/devtools/client/performance/test/browser_profiler_tree-view-08.js
+++ b/devtools/client/performance/test/browser_profiler_tree-view-08.js
@@ -38,31 +38,31 @@ function test() {
   let JS = treeRoot.getChild(1);
   let GC = A.getChild(1);
   let JS2 = A.getChild(2).getChild().getChild();
 
   is(JS.target.getAttribute("category"), "js",
     "Generalized JS node has correct category");
   is(JS.target.getAttribute("tooltiptext"), "JIT",
     "Generalized JS node has correct category");
-  is(JS.target.querySelector(".call-tree-name").getAttribute("value"), "JIT",
+  is(JS.target.querySelector(".call-tree-name").textContent.trim(), "JIT",
     "Generalized JS node has correct display value as just the category name.");
 
   is(JS2.target.getAttribute("category"), "js",
     "Generalized second JS node has correct category");
   is(JS2.target.getAttribute("tooltiptext"), "JIT",
     "Generalized second JS node has correct category");
-  is(JS2.target.querySelector(".call-tree-name").getAttribute("value"), "JIT",
+  is(JS2.target.querySelector(".call-tree-name").textContent.trim(), "JIT",
     "Generalized second JS node has correct display value as just the category name.");
 
   is(GC.target.getAttribute("category"), "gc",
     "Generalized GC node has correct category");
   is(GC.target.getAttribute("tooltiptext"), "GC",
     "Generalized GC node has correct category");
-  is(GC.target.querySelector(".call-tree-name").getAttribute("value"), "GC",
+  is(GC.target.querySelector(".call-tree-name").textContent.trim(), "GC",
     "Generalized GC node has correct display value as just the category name.");
 
   finish();
 }
 
 var gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
--- a/devtools/client/performance/test/browser_profiler_tree-view-10.js
+++ b/devtools/client/performance/test/browser_profiler_tree-view-10.js
@@ -58,19 +58,19 @@ function test() {
     [ 40,  0, "    A"],
     [ 10, 10, "B"],
     [ 10,  0, "  A"],
   ].forEach(function (def, i) {
     info(`Checking ${i}th tree item`);
     let [total, self, name] = def;
     name = name.trim();
 
-    is($$name(i).getAttribute("value"), name, `${name} has correct name.`);
-    is($$percentage(i).getAttribute("value"), `${total}%`, `${name} has correct total percent.`);
-    is($$selfpercentage(i).getAttribute("value"), `${self}%`, `${name} has correct self percent.`);
+    is($$name(i).textContent.trim(), name, `${name} has correct name.`);
+    is($$percentage(i).textContent.trim(), `${total}%`, `${name} has correct total percent.`);
+    is($$selfpercentage(i).textContent.trim(), `${self}%`, `${name} has correct self percent.`);
   });
 
   finish();
 }
 
 var gThread = synthesizeProfileForTest([{
   time: 5,
   frames: [
--- a/devtools/client/performance/test/browser_profiler_tree-view-11.js
+++ b/devtools/client/performance/test/browser_profiler_tree-view-11.js
@@ -26,17 +26,17 @@ function* spawnTest() {
 
   yield DetailsView.selectView("js-calltree");
 
   yield injectAndRenderProfilerData();
 
   let rows = $$("#js-calltree-view .call-tree-item");
   is(rows.length, 4, "4 call tree rows exist");
   for (let row of rows) {
-    let name = $(".call-tree-name", row).value;
+    let name = $(".call-tree-name", row).textContent.trim();
     switch (name) {
       case "A":
         ok($(".opt-icon", row), "found an opt icon on a leaf node with opt data");
         break;
       case "C":
         ok(!$(".opt-icon", row), "frames without opt data do not have an icon");
         break;
       case "Gecko":
--- a/devtools/client/shared/widgets/AbstractTreeItem.jsm
+++ b/devtools/client/shared/widgets/AbstractTreeItem.jsm
@@ -517,17 +517,16 @@ AbstractTreeItem.prototype = {
       this.collapse();
     }
   },
 
   /**
    * Handler for the "click" event on the element displaying this tree item.
    */
   _onClick: function(e) {
-    e.preventDefault();
     e.stopPropagation();
     this.focus();
   },
 
   /**
    * Handler for the "dblclick" event on the element displaying this tree item.
    */
   _onDoubleClick: function(e) {
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -264,16 +264,26 @@
 .call-tree-cell:not(:last-child) {
   text-align: end;
 }
 
 .call-tree-header {
   background-color: var(--theme-tab-toolbar-background);
 }
 
+.call-tree-item .call-tree-cell,
+.call-tree-item .call-tree-cell[type=function] description {
+  -moz-user-select: text;
+}
+
+.call-tree-item .call-tree-cell::-moz-selection,
+.call-tree-item .call-tree-cell[type=function] description::-moz-selection {
+  background-color: var(--theme-highlight-orange);
+}
+
 .call-tree-item:last-child {
   border-bottom: 1px solid var(--cell-border-color);
 }
 
 .call-tree-item:nth-child(2n) {
   background-color: var(--row-alt-background-color);
 }