Bug 843187 - Variables view: going down through the properties via keyboard is really broken, r=vporof

This commit is contained in:
J. Ryan Stinnett 2013-04-23 07:34:08 -05:00
Родитель ea3af34653
Коммит 9573fbd0a8
2 изменённых файлов: 171 добавлений и 54 удалений

Просмотреть файл

@ -653,12 +653,12 @@ function testKeyboardAccessibility(callback) {
"The 0 item should be focused now."); "The 0 item should be focused now.");
EventUtils.sendKey("END", gDebugger); EventUtils.sendKey("END", gDebugger);
is(gVariablesView.getFocusedItem().name, "foo", is(gVariablesView.getFocusedItem().name, "bar",
"The foo item should be focused now."); "The bar item should be focused now.");
EventUtils.sendKey("DOWN", gDebugger); EventUtils.sendKey("DOWN", gDebugger);
is(gVariablesView.getFocusedItem().name, "bar", is(gVariablesView.getFocusedItem().name, "bar",
"The bar item should be focused now."); "The bar item should still be focused now.");
EventUtils.sendKey("UP", gDebugger); EventUtils.sendKey("UP", gDebugger);
is(gVariablesView.getFocusedItem().name, "foo", is(gVariablesView.getFocusedItem().name, "foo",
@ -669,10 +669,14 @@ function testKeyboardAccessibility(callback) {
"The foo item should still be focused now."); "The foo item should still be focused now.");
EventUtils.sendKey("PAGE_DOWN", gDebugger); EventUtils.sendKey("PAGE_DOWN", gDebugger);
is(gVariablesView.getFocusedItem().name, "foo", is(gVariablesView.getFocusedItem().name, "bar",
"The foo item should still be focused now."); "The bar item should be focused now.");
EventUtils.sendKey("PAGE_UP", gDebugger); EventUtils.sendKey("PAGE_UP", gDebugger);
is(gVariablesView.getFocusedItem().name, "someProp7",
"The someProp7 item should be focused now.");
EventUtils.sendKey("UP", gDebugger);
is(gVariablesView.getFocusedItem().name, "__proto__", is(gVariablesView.getFocusedItem().name, "__proto__",
"The __proto__ item should be focused now."); "The __proto__ item should be focused now.");
@ -684,10 +688,6 @@ function testKeyboardAccessibility(callback) {
is(gVariablesView.getFocusedItem().name, "get", is(gVariablesView.getFocusedItem().name, "get",
"The get item should be focused now."); "The get item should be focused now.");
EventUtils.sendKey("UP", gDebugger);
is(gVariablesView.getFocusedItem().name, "p8",
"The p8 item should be focused now.");
EventUtils.sendKey("HOME", gDebugger); EventUtils.sendKey("HOME", gDebugger);
is(gVariablesView.getFocusedItem().name, "someProp0", is(gVariablesView.getFocusedItem().name, "someProp0",
"The someProp0 item should be focused now."); "The someProp0 item should be focused now.");
@ -828,6 +828,18 @@ function testKeyboardAccessibility(callback) {
is(gVariablesView.getFocusedItem().expanded, false, is(gVariablesView.getFocusedItem().expanded, false,
"The top-level __proto__ item should not be expanded."); "The top-level __proto__ item should not be expanded.");
EventUtils.sendKey("END", gDebugger);
is(gVariablesView.getFocusedItem().name, "foo",
"The foo scope should be focused.");
EventUtils.sendKey("PAGE_UP", gDebugger);
is(gVariablesView.getFocusedItem().name, "__proto__",
"The __proto__ property should be focused.");
EventUtils.sendKey("PAGE_DOWN", gDebugger);
is(gVariablesView.getFocusedItem().name, "foo",
"The foo scope should be focused.");
executeSoon(callback); executeSoon(callback);
}); });
}); });

Просмотреть файл

@ -52,6 +52,7 @@ const STR = Services.strings.createBundle(DBG_STRINGS_URI);
*/ */
this.VariablesView = function VariablesView(aParentNode, aFlags = {}) { this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
this._store = new Map(); this._store = new Map();
this._items = [];
this._itemsByElement = new WeakMap(); this._itemsByElement = new WeakMap();
this._prevHierarchy = new Map(); this._prevHierarchy = new Map();
this._currHierarchy = new Map(); this._currHierarchy = new Map();
@ -103,6 +104,7 @@ VariablesView.prototype = {
let scope = new Scope(this, aName); let scope = new Scope(this, aName);
this._store.set(scope.id, scope); this._store.set(scope.id, scope);
this._items.push(scope);
this._currHierarchy.set(aName, scope); this._currHierarchy.set(aName, scope);
this._itemsByElement.set(scope._target, scope); this._itemsByElement.set(scope._target, scope);
scope.header = !!aName; scope.header = !!aName;
@ -135,6 +137,7 @@ VariablesView.prototype = {
} }
this._store.clear(); this._store.clear();
this._items.length = 0;
this._itemsByElement.clear(); this._itemsByElement.clear();
this._appendEmptyNotice(); this._appendEmptyNotice();
@ -161,6 +164,7 @@ VariablesView.prototype = {
let currList = this._list = this.document.createElement("scrollbox"); let currList = this._list = this.document.createElement("scrollbox");
this._store.clear(); this._store.clear();
this._items.length = 0;
this._itemsByElement.clear(); this._itemsByElement.clear();
this._emptyTimeout = this.window.setTimeout(function() { this._emptyTimeout = this.window.setTimeout(function() {
@ -529,64 +533,73 @@ VariablesView.prototype = {
}, },
/** /**
* Focuses the first visible variable or property in this container. * Find the first item in the tree of visible items in this container that
* matches the predicate. Searches in visual order (the order seen by the
* user). Descends into each scope to check the scope and its children.
*
* @param function aPredicate
* A function that returns true when a match is found.
* @return Scope | Variable | Property
* The first visible scope, variable or property, or null if nothing
* is found.
*/
_findInVisibleItems: function VV__findInVisibleItems(aPredicate) {
for (let scope of this._items) {
let result = scope._findInVisibleItems(aPredicate);
if (result) {
return result;
}
}
return null;
},
/**
* Find the last item in the tree of visible items in this container that
* matches the predicate. Searches in reverse visual order (opposite of the
* order seen by the user). Descends into each scope to check the scope and
* its children.
*
* @param function aPredicate
* A function that returns true when a match is found.
* @return Scope | Variable | Property
* The last visible scope, variable or property, or null if nothing
* is found.
*/
_findInVisibleItemsReverse: function VV__findInVisibleItemsReverse(aPredicate) {
for (let i = this._items.length - 1; i >= 0; i--) {
let scope = this._items[i];
let result = scope._findInVisibleItemsReverse(aPredicate);
if (result) {
return result;
}
}
return null;
},
/**
* Focuses the first visible scope, variable, or property in this container.
*/ */
focusFirstVisibleNode: function VV_focusFirstVisibleNode() { focusFirstVisibleNode: function VV_focusFirstVisibleNode() {
let property, variable, scope; let focusableItem = this._findInVisibleItems(item => item.focusable);
for (let [, item] of this._currHierarchy) { if (focusableItem) {
if (!item.focusable) { this._focusItem(focusableItem);
continue;
}
if (item instanceof Property) {
property = item;
break;
} else if (item instanceof Variable) {
variable = item;
break;
} else if (item instanceof Scope) {
scope = item;
break;
}
}
if (scope) {
this._focusItem(scope);
} else if (variable) {
this._focusItem(variable);
} else if (property) {
this._focusItem(property);
} }
this._parent.scrollTop = 0; this._parent.scrollTop = 0;
this._parent.scrollLeft = 0; this._parent.scrollLeft = 0;
}, },
/** /**
* Focuses the last visible variable or property in this container. * Focuses the last visible scope, variable, or property in this container.
*/ */
focusLastVisibleNode: function VV_focusLastVisibleNode() { focusLastVisibleNode: function VV_focusLastVisibleNode() {
let property, variable, scope; let focusableItem = this._findInVisibleItemsReverse(item => item.focusable);
for (let [, item] of this._currHierarchy) { if (focusableItem) {
if (!item.focusable) { this._focusItem(focusableItem);
continue;
} }
if (item instanceof Property) {
property = item;
} else if (item instanceof Variable) {
variable = item;
} else if (item instanceof Scope) {
scope = item;
}
}
if (property && (!variable || property.isDescendantOf(variable))) {
this._focusItem(property);
} else if (variable && (!scope || variable.isDescendantOf(scope))) {
this._focusItem(variable);
} else if (scope) {
this._focusItem(scope);
this._parent.scrollTop = this._parent.scrollHeight; this._parent.scrollTop = this._parent.scrollHeight;
this._parent.scrollLeft = 0; this._parent.scrollLeft = 0;
}
}, },
/** /**
@ -888,6 +901,7 @@ VariablesView.prototype = {
_window: null, _window: null,
_store: null, _store: null,
_items: null,
_prevHierarchy: null, _prevHierarchy: null,
_currHierarchy: null, _currHierarchy: null,
_enumVisible: true, _enumVisible: true,
@ -1082,6 +1096,8 @@ function Scope(aView, aName, aFlags = {}) {
this.separatorStr = aView.separatorStr; this.separatorStr = aView.separatorStr;
this._store = new Map(); this._store = new Map();
this._enumItems = [];
this._nonEnumItems = [];
this._init(aName.trim(), aFlags); this._init(aName.trim(), aFlags);
} }
@ -1785,6 +1801,89 @@ Scope.prototype = {
return null; return null;
}, },
/**
* Find the first item in the tree of visible items in this item that matches
* the predicate. Searches in visual order (the order seen by the user).
* Tests itself, then descends into first the enumerable children and then
* the non-enumerable children (since they are presented in separate groups).
*
* @param function aPredicate
* A function that returns true when a match is found.
* @return Scope | Variable | Property
* The first visible scope, variable or property, or null if nothing
* is found.
*/
_findInVisibleItems: function S__findInVisibleItems(aPredicate) {
if (aPredicate(this)) {
return this;
}
if (this._isExpanded) {
if (this._variablesView._enumVisible) {
for (let item of this._enumItems) {
let result = item._findInVisibleItems(aPredicate);
if (result) {
return result;
}
}
}
if (this._variablesView._nonEnumVisible) {
for (let item of this._nonEnumItems) {
let result = item._findInVisibleItems(aPredicate);
if (result) {
return result;
}
}
}
}
return null;
},
/**
* Find the last item in the tree of visible items in this item that matches
* the predicate. Searches in reverse visual order (opposite of the order
* seen by the user). Descends into first the non-enumerable children, then
* the enumerable children (since they are presented in separate groups), and
* finally tests itself.
*
* @param function aPredicate
* A function that returns true when a match is found.
* @return Scope | Variable | Property
* The last visible scope, variable or property, or null if nothing
* is found.
*/
_findInVisibleItemsReverse: function S__findInVisibleItemsReverse(aPredicate) {
if (this._isExpanded) {
if (this._variablesView._nonEnumVisible) {
for (let i = this._nonEnumItems.length - 1; i >= 0; i--) {
let item = this._nonEnumItems[i];
let result = item._findInVisibleItemsReverse(aPredicate);
if (result) {
return result;
}
}
}
if (this._variablesView._enumVisible) {
for (let i = this._enumItems.length - 1; i >= 0; i--) {
let item = this._enumItems[i];
let result = item._findInVisibleItemsReverse(aPredicate);
if (result) {
return result;
}
}
}
}
if (aPredicate(this)) {
return this;
}
return null;
},
/** /**
* Gets top level variables view instance. * Gets top level variables view instance.
* @return VariablesView * @return VariablesView
@ -1854,7 +1953,9 @@ Scope.prototype = {
_name: null, _name: null,
_title: null, _title: null,
_enum: null, _enum: null,
_enumItems: null,
_nonenum: null, _nonenum: null,
_nonEnumItems: null,
_throbber: null _throbber: null
}; };
@ -2167,8 +2268,10 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
this._nameString == "this" || this._nameString == "this" ||
this._nameString == "<exception>") { this._nameString == "<exception>") {
this.ownerView._lazyAppend(aImmediateFlag, true, this._target); this.ownerView._lazyAppend(aImmediateFlag, true, this._target);
this.ownerView._enumItems.push(this);
} else { } else {
this.ownerView._lazyAppend(aImmediateFlag, false, this._target); this.ownerView._lazyAppend(aImmediateFlag, false, this._target);
this.ownerView._nonEnumItems.push(this);
} }
}, },
@ -2674,8 +2777,10 @@ ViewHelpers.create({ constructor: Property, proto: Variable.prototype }, {
_onInit: function P__onInit(aImmediateFlag) { _onInit: function P__onInit(aImmediateFlag) {
if (this._initialDescriptor.enumerable) { if (this._initialDescriptor.enumerable) {
this.ownerView._lazyAppend(aImmediateFlag, true, this._target); this.ownerView._lazyAppend(aImmediateFlag, true, this._target);
this.ownerView._enumItems.push(this);
} else { } else {
this.ownerView._lazyAppend(aImmediateFlag, false, this._target); this.ownerView._lazyAppend(aImmediateFlag, false, this._target);
this.ownerView._nonEnumItems.push(this);
} }
} }
}); });