Bug 1400963 - Scroll selected row into view in JSON Viewer.
MozReview-Commit-ID: I7QuuGzHaiA
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -38,16 +38,17 @@ skip-if = (os == 'linux' && bits == 32 &
[browser_jsonview_empty_object.js]
[browser_jsonview_encoding.js]
[browser_jsonview_filter.js]
[browser_jsonview_invalid_json.js]
[browser_jsonview_manifest.js]
[browser_jsonview_nojs.js]
[browser_jsonview_nul.js]
[browser_jsonview_object-type.js]
+[browser_jsonview_row_selection.js]
[browser_jsonview_save_json.js]
support-files =
!/toolkit/content/tests/browser/common/mockTransfer.js
[browser_jsonview_theme.js]
[browser_jsonview_slash.js]
[browser_jsonview_valid_json.js]
[browser_json_refresh.js]
[browser_jsonview_serviceworker.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_row_selection.js
@@ -0,0 +1,44 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+ info("Test JSON row selection started");
+
+ // Create a tall JSON so that there is a scrollbar.
+ let numRows = 1e3;
+ let json = JSON.stringify(Array(numRows).fill().map((_, i) => i));
+ let tab = yield addJsonViewTab("data:application/json," + json);
+
+ is(yield getElementCount(".treeRow"), numRows, "Got the expected number of rows.");
+ yield assertRowSelected(null);
+ yield evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
+ ok(yield evalInContent("scroller.clientHeight < scroller.scrollHeight"),
+ "There is a scrollbar.");
+ is(yield evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
+
+ // Click to select last row.
+ yield evalInContent("$('.treeRow:last-child').click()");
+ yield assertRowSelected(numRows);
+ is(yield evalInContent("scroller.scrollTop + scroller.clientHeight"),
+ yield evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
+
+ // Click to select 2nd row.
+ yield evalInContent("$('.treeRow:nth-child(2)').click()");
+ yield assertRowSelected(2);
+ ok(yield evalInContent("scroller.scrollTop > 0"), "Not scrolled to the top.");
+
+ // Synthetize up arrow key to select first row.
+ yield evalInContent("$('.treeTable').focus()");
+ yield BrowserTestUtils.synthesizeKey("VK_UP", {}, tab.linkedBrowser);
+ yield assertRowSelected(1);
+ is(yield evalInContent("scroller.scrollTop"), 0, "Scrolled to the top.");
+});
+
+async function assertRowSelected(rowNum) {
+ let idx = evalInContent("[].indexOf.call($$('.treeRow'), $('.treeRow.selected'))");
+ is(await idx + 1, +rowNum, `${rowNum ? "The row #" + rowNum : "No row"} is selected.`);
+}
--- a/devtools/client/jsonview/test/doc_frame_script.js
+++ b/devtools/client/jsonview/test/doc_frame_script.js
@@ -114,8 +114,13 @@ addMessageListener("Test:JsonView:WaitFo
observer.observe(firstRow, { attributes: true });
});
addMessageListener("Test:JsonView:Eval", function (msg) {
let result = content.eval(msg.data.code);
sendAsyncMessage(msg.name, {result});
});
+
+Components.utils.exportFunction(content.document.querySelector.bind(content.document),
+ content, {defineAs: "$"});
+Components.utils.exportFunction(content.document.querySelectorAll.bind(content.document),
+ content, {defineAs: "$$"});
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -4,16 +4,17 @@
* 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";
// Make this available to both AMD and CJS environments
define(function (require, exports, module) {
const { cloneElement, Component, createFactory, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
+ const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
// Reps
const { ObjectProvider } = require("./ObjectProvider");
const TreeRow = createFactory(require("./TreeRow"));
const TreeHeader = createFactory(require("./TreeHeader"));
const defaultProps = {
object: null,
@@ -148,17 +149,17 @@ define(function (require, exports, modul
}
componentDidUpdate() {
let selected = this.getSelectedRow(this.rows);
if (!selected && this.rows.length > 0) {
// TODO: Do better than just selecting the first row again. We want to
// select (in order) previous, next or parent in case when selected
// row is removed.
- this.selectRow(this.rows[0].props.member.path);
+ this.selectRow(this.rows[0]);
}
}
static subPath(path, subKey) {
return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
}
/**
@@ -250,52 +251,54 @@ define(function (require, exports, modul
case "ArrowLeft":
if (row && row.props.member.open) {
this.toggle(this.state.selected);
}
break;
case "ArrowDown":
let nextRow = this.rows[index + 1];
if (nextRow) {
- this.selectRow(nextRow.props.member.path);
+ this.selectRow(nextRow);
}
break;
case "ArrowUp":
let previousRow = this.rows[index - 1];
if (previousRow) {
- this.selectRow(previousRow.props.member.path);
+ this.selectRow(previousRow);
}
break;
default:
return;
}
event.preventDefault();
}
onClickRow(nodePath, event) {
event.stopPropagation();
let cell = event.target.closest("td");
if (cell && cell.classList.contains("treeLabelCell")) {
this.toggle(nodePath);
}
- this.selectRow(nodePath);
+ this.selectRow(event.currentTarget);
}
getSelectedRow(rows) {
if (!this.state.selected || rows.length === 0) {
return null;
}
return rows.find(row => this.isSelected(row.props.member.path));
}
- selectRow(nodePath) {
+ selectRow(row) {
+ row = findDOMNode(row);
this.setState(Object.assign({}, this.state, {
- selected: nodePath
+ selected: row.id
}));
+ row.scrollIntoView({block: "nearest"});
}
isSelected(nodePath) {
return nodePath === this.state.selected;
}
// Filtering & Sorting