Bug 1243131 - memory tool: select snapshot using ACCEL+{UP/DOWN};r=fitzgen

Adds a keydown listener on the memory panel window. Select previous/next snapshot
when user presses UP/DOWN with the accelKey modifier (metaKey on OSX, ctrlKey on
windows).

Keydown events with modifiers are no longer listened to by the tree node elements.

Updated tree node test.
Added new mochitest to test the new keyboard navigation on the census view. )
This commit is contained in:
Julian Descottes 2016-02-12 11:03:34 +01:00
parent 05d335ab9c
commit 8a5bb348c4
6 changed files with 185 additions and 0 deletions

View File

@ -3,6 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const { assert } = require("devtools/shared/DevToolsUtils");
const { appinfo } = require("Services");
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { breakdowns, diffingState, viewState } = require("./constants");
@ -53,6 +54,17 @@ const MemoryApp = createClass({
return {};
},
componentDidMount() {
// Attach the keydown listener directly to the window. When an element that
// has the focus (such as a tree node) is removed from the DOM, the focus
// falls back to the body.
window.addEventListener("keydown", this.onKeyDown);
},
componentWillUnmount() {
window.removeEventListener("keydown", this.onKeyDown);
},
childContextTypes: {
front: PropTypes.any,
heapWorker: PropTypes.any,
@ -67,6 +79,27 @@ const MemoryApp = createClass({
};
},
onKeyDown(e) {
let { snapshots, dispatch, heapWorker } = this.props;
const selectedSnapshot = snapshots.find(s => s.selected);
const selectedIndex = snapshots.indexOf(selectedSnapshot);
let isOSX = appinfo.OS == "Darwin";
let isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey);
// On ACCEL+UP, select previous snapshot.
if (isAccelKey && e.key === "ArrowUp") {
let previousIndex = Math.max(0, selectedIndex - 1);
let previousSnapshotId = snapshots[previousIndex].id;
dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId));
}
// On ACCEL+DOWN, select next snapshot.
if (isAccelKey && e.key === "ArrowDown") {
let nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1);
let nextSnapshotId = snapshots[nextIndex].id;
dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId));
}
},
render() {
let {
dispatch,

View File

@ -15,6 +15,7 @@ support-files =
[browser_memory_dominator_trees_02.js]
[browser_memory_filter_01.js]
[browser_memory_keyboard.js]
[browser_memory_keyboard-snapshot-list.js]
[browser_memory_no_allocation_stacks.js]
[browser_memory_no_auto_expand.js]
skip-if = debug # bug 1219554

View File

@ -0,0 +1,94 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that using ACCEL+UP/DOWN, the user can navigate between snapshots.
"use strict";
const {
snapshotState
} = require("devtools/client/memory/constants");
const {
takeSnapshotAndCensus
} = require("devtools/client/memory/actions/snapshot");
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
this.test = makeMemoryTest(TEST_URL, function* ({ panel }) {
// Creating snapshots already takes ~25 seconds on linux 32 debug machines
// which makes the test very likely to go over the allowed timeout
requestLongerTimeout(2);
const heapWorker = panel.panelWin.gHeapAnalysesClient;
const front = panel.panelWin.gFront;
const store = panel.panelWin.gStore;
const { dispatch } = store;
const doc = panel.panelWin.document;
info("Take 3 snapshots");
dispatch(takeSnapshotAndCensus(front, heapWorker));
dispatch(takeSnapshotAndCensus(front, heapWorker));
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilState(store, state =>
state.snapshots.length == 3 &&
state.snapshots.every(s => s.state === snapshotState.SAVED_CENSUS));
ok(true, "All snapshots are in SAVED_CENSUS state");
yield waitUntilSnapshotSelected(store, 2);
ok(true, "Third snapshot selected after creating all snapshots.");
info("Press ACCEL+UP key, expect second snapshot selected.");
EventUtils.synthesizeKey("VK_UP", { accelKey: true }, panel.panelWin);
yield waitUntilSnapshotSelected(store, 1);
ok(true, "Second snapshot selected after alt+UP.");
info("Press ACCEL+UP key, expect first snapshot selected.");
EventUtils.synthesizeKey("VK_UP", { accelKey: true }, panel.panelWin);
yield waitUntilSnapshotSelected(store, 0);
ok(true, "First snapshot is selected after ACCEL+UP");
info("Check ACCEL+UP is a noop when the first snapshot is selected.");
EventUtils.synthesizeKey("VK_UP", { accelKey: true }, panel.panelWin);
// We assume the snapshot selection should be synchronous here.
is(getSelectedSnapshotIndex(store), 0, "First snapshot is still selected");
info("Press ACCEL+DOWN key, expect second snapshot selected.");
EventUtils.synthesizeKey("VK_DOWN", { accelKey: true }, panel.panelWin);
yield waitUntilSnapshotSelected(store, 1);
ok(true, "Second snapshot is selected after ACCEL+DOWN");
info("Click on first node.");
let firstNode = doc.querySelector(".tree .heap-tree-item-name");
EventUtils.synthesizeMouseAtCenter(firstNode, {}, panel.panelWin);
yield waitUntilState(store, state => state.snapshots[1].census.focused ===
state.snapshots[1].census.report.children[0]
);
ok(true, "First root is selected after click.");
info("Press DOWN key, expect second root focused.");
EventUtils.synthesizeKey("VK_DOWN", {}, panel.panelWin);
yield waitUntilState(store, state => state.snapshots[1].census.focused ===
state.snapshots[1].census.report.children[1]
);
ok(true, "Second root is selected after pressing DOWN.");
is(getSelectedSnapshotIndex(store), 1, "Second snapshot is still selected");
info("Press UP key, expect second root focused.");
EventUtils.synthesizeKey("VK_UP", {}, panel.panelWin);
yield waitUntilState(store, state => state.snapshots[1].census.focused ===
state.snapshots[1].census.report.children[0]
);
ok(true, "First root is selected after pressing UP.");
is(getSelectedSnapshotIndex(store), 1, "Second snapshot is still selected");
info("Press ACCEL+DOWN key, expect third snapshot selected.");
EventUtils.synthesizeKey("VK_DOWN", { accelKey: true }, panel.panelWin);
yield waitUntilSnapshotSelected(store, 2);
ok(true, "Thirdˆ snapshot is selected after ACCEL+DOWN");
info("Check ACCEL+DOWN is a noop when the last snapshot is selected.");
EventUtils.synthesizeKey("VK_DOWN", { accelKey: true }, panel.panelWin);
// We assume the snapshot selection should be synchronous here.
is(getSelectedSnapshotIndex(store), 2, "Third snapshot is still selected");
});

View File

@ -142,3 +142,26 @@ function getDisplayedSnapshotStatus(document) {
const status = document.querySelector(".snapshot-status");
return status ? status.textContent.trim() : null;
}
/**
* Get the index of the currently selected snapshot.
*
* @return {Number}
*/
function getSelectedSnapshotIndex(store) {
let snapshots = store.getState().snapshots;
let selectedSnapshot = snapshots.find(s => s.selected);
return snapshots.indexOf(selectedSnapshot);
}
/**
* Returns a promise that will resolve when the snapshot with provided index
* becomes selected.
*
* @return {Promise}
*/
function waitUntilSnapshotSelected(store, snapshotIndex) {
return waitUntilState(store, state =>
state.snapshots[snapshotIndex] &&
state.snapshots[snapshotIndex].selected === true);
}

View File

@ -276,6 +276,35 @@ window.onload = Task.async(function* () {
"-N:false",
"--O:false",
], "After the RIGHT, K should be focused.");
// Check that keys are ignored if any modifier is present.
let keysWithModifier = [
{ key: "ArrowDown", altKey: true },
{ key: "ArrowDown", ctrlKey: true },
{ key: "ArrowDown", metaKey: true },
{ key: "ArrowDown", shiftKey: true },
];
for (let key of keysWithModifier) {
Simulate.keyDown(document.querySelector(".tree"), key);
yield forceRender(tree);
isRenderedTree(document.body.textContent, [
"A:false",
"-B:false",
"--E:false",
"---K:true",
"---L:false",
"--F:false",
"--G:false",
"-C:false",
"--H:false",
"--I:false",
"-D:false",
"--J:false",
"M:false",
"-N:false",
"--O:false",
], "After DOWN + (alt|ctrl|meta|shift), K should remain focused.");
}
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {

View File

@ -405,6 +405,11 @@ const Tree = module.exports = createClass({
return;
}
// Allow parent nodes to use navigation arrows with modifiers.
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
return;
}
// Prevent scrolling when pressing navigation keys. Guard against mocked
// events received when testing.
if (e.nativeEvent && e.nativeEvent.preventDefault) {