Bug 798874 - The variables view should be filterable, r=past

This commit is contained in:
Victor Porof 2012-11-04 01:01:05 +02:00
parent eafa7e9651
commit 49de12908c
11 changed files with 1079 additions and 9 deletions

View File

@ -29,6 +29,9 @@ function VariablesView(aParentNode) {
this._parent = aParentNode;
this._appendEmptyNotice();
this._onSearchboxInput = this._onSearchboxInput.bind(this);
this._onSearchboxKeyPress = this._onSearchboxKeyPress.bind(this);
// Create an internal list container.
this._list = this.document.createElement("vbox");
this._parent.appendChild(this._list);
@ -140,6 +143,97 @@ VariablesView.prototype = {
}
},
/**
* Enables variable and property searching in this view.
*/
enableSearch: function VV_enableSearch() {
// If searching was already enabled, no need to re-enable it again.
if (this._searchboxContainer) {
return;
}
let document = this.document;
let parent = this._parent;
let container = this._searchboxContainer = document.createElement("hbox");
container.className = "devtools-toolbar";
let searchbox = this._searchboxNode = document.createElement("textbox");
searchbox.className = "devtools-searchinput";
searchbox.setAttribute("placeholder", this._searchboxPlaceholder);
searchbox.setAttribute("type", "search");
searchbox.setAttribute("flex", "1");
searchbox.addEventListener("input", this._onSearchboxInput, false);
searchbox.addEventListener("keypress", this._onSearchboxKeyPress, false);
container.appendChild(searchbox);
parent.insertBefore(container, parent.firstChild);
},
/**
* Disables variable and property searching in this view.
*/
disableSearch: function VV_disableSearch() {
// If searching was already disabled, no need to re-disable it again.
if (!this._searchboxContainer) {
return;
}
this._parent.removeChild(this._searchboxContainer);
this._searchboxNode.addEventListener("input", this._onSearchboxInput, false);
this._searchboxNode.addEventListener("keypress", this._onSearchboxKeyPress, false);
this._searchboxContainer = null;
this._searchboxNode = null;
},
/**
* Performs a case insensitive search for variables or properties matching
* the query, and hides non-matched items.
*
* @param string aQuery
* The variable or property to search for.
*/
performSearch: function VV_performSerch(aQuery) {
let lowerCaseQuery = aQuery.toLowerCase();
for (let [_, scope] in this) {
scope._performSearch(lowerCaseQuery);
}
},
/**
* Sets the text displayed for the searchbox in this container.
* @param string aValue
*/
set searchPlaceholder(aValue) {
if (this._searchboxNode) {
this._searchboxNode.setAttribute("placeholder", aValue);
}
this._searchboxPlaceholder = aValue;
},
/**
* Listener handling the searchbox input event.
*/
_onSearchboxInput: function VV__onSearchboxInput() {
this.performSearch(this._searchboxNode.value);
},
/**
* Listener handling the searchbox key press event.
*/
_onSearchboxKeyPress: function VV__onSearchboxKeyPress(e) {
switch(e.keyCode) {
case e.DOM_VK_RETURN:
case e.DOM_VK_ENTER:
this._onSearchboxInput();
return;
case e.DOM_VK_ESCAPE:
this._searchboxNode.value = "";
this._onSearchboxInput();
return;
}
},
/**
* Sets the text displayed in this container when there are no available items.
* @param string aValue
@ -205,8 +299,11 @@ VariablesView.prototype = {
_emptyTimeout: null,
_enumVisible: true,
_nonEnumVisible: true,
_list: null,
_parent: null,
_list: null,
_searchboxNode: null,
_searchboxContainer: null,
_searchboxPlaceholder: "",
_emptyTextNode: null,
_emptyTextValue: ""
};
@ -353,6 +450,7 @@ Scope.prototype = {
* Toggles between the scope's collapsed and expanded state.
*/
toggle: function S_toggle() {
this._wasToggled = true;
this.expanded ^= 1;
if (this.ontoggle) {
@ -388,6 +486,12 @@ Scope.prototype = {
*/
get expanded() this._isExpanded,
/**
* Returns if this element was ever toggled.
* @return boolean
*/
get toggled() this._wasToggled,
/**
* Gets the twisty visibility state.
* @return boolean
@ -531,6 +635,56 @@ Scope.prototype = {
}
},
/**
* Performs a case insensitive search for variables or properties matching
* the query, and hides non-matched items.
*
* @param string aLowerCaseQuery
* The lowercased name of the variable or property to search for.
*/
_performSearch: function S__performSearch(aLowerCaseQuery) {
for (let [_, variable] in this) {
let currentObject = variable;
let lowerCaseName = variable._nameString.toLowerCase();
let lowerCaseValue = variable._valueString.toLowerCase();
// Non-matched variables or properties require a corresponding attribute.
if (!lowerCaseName.contains(aLowerCaseQuery) &&
!lowerCaseValue.contains(aLowerCaseQuery)) {
variable.target.setAttribute("non-match", "");
}
// Variable or property is matched.
else {
variable.target.removeAttribute("non-match");
// If the variable was ever expanded, there's a possibility it may
// contain some matched properties, so make sure they're visible
// ("expand downwards").
if (variable.toggled) {
variable.expand(true);
}
// If the variable is contained in another scope (variable or property),
// the parent may not be a match, thus hidden. It should be visible
// ("expand upwards").
while ((variable = variable.ownerView) && /* Parent object exists. */
(variable instanceof Scope ||
variable instanceof Variable ||
variable instanceof Property)) {
// Show and expand the parent, as it is certainly accessible.
variable.target.removeAttribute("non-match");
variable.expand(true);
}
}
// Proceed with the search recursively inside this variable or property.
currentObject._performSearch(aLowerCaseQuery);
}
},
/**
* Gets top level variables view instance.
* @return VariablesView
@ -569,10 +723,11 @@ Scope.prototype = {
_locked: false,
_isShown: true,
_isExpanded: false,
_wasToggled: false,
_isArrowVisible: true,
_store: null,
_idString: null,
_nameString: null,
_idString: "",
_nameString: "",
_target: null,
_arrow: null,
_name: null,

View File

@ -1212,7 +1212,7 @@ SourceResults.prototype = {
*/
toggle: function SR_toggle(e) {
if (e instanceof Event) {
this._toggled = true;
this._userToggled = true;
}
this.expanded ^= 1;
},
@ -1230,9 +1230,10 @@ SourceResults.prototype = {
set expanded(aFlag) this[aFlag ? "expand" : "collapse"](),
/**
* Returns true if this element was toggled via user interaction.
* Returns if this element was ever toggled via user interaction.
* @return boolean
*/
get toggled() this._toggled,
get toggled() this._userToggled,
/**
* Gets the element associated with this item.
@ -1313,7 +1314,7 @@ SourceResults.prototype = {
_store: null,
_target: null,
_toggled: false
_userToggled: false
};
/**

View File

@ -38,6 +38,8 @@ let DebuggerView = {
this.GlobalSearch.initialize();
this.Variables = new VariablesView(document.getElementById("variables"));
this.Variables.enableSearch();
this.Variables.searchPlaceholder = L10N.getStr("emptyVariablesFilterText");
this.Variables.emptyText = L10N.getStr("emptyVariablesText");
this.Variables.nonEnumVisible = Prefs.nonEnumVisible;
this.Variables.eval = DebuggerController.StackFrames.evaluate;

View File

@ -63,6 +63,15 @@
display: -moz-box;
}
/**
* Variables and properties searching
*/
.variable[non-match] > .title,
.property[non-match] > .title {
display: none;
}
/**
* Toolbar
*/

View File

@ -33,6 +33,8 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_propertyview-09.js \
browser_dbg_propertyview-10.js \
browser_dbg_propertyview-edit.js \
browser_dbg_propertyview-filter-01.js \
browser_dbg_propertyview-filter-02.js \
browser_dbg_propertyview-reexpand.js \
browser_dbg_reload-same-script.js \
browser_dbg_pane-collapse.js \

View File

@ -0,0 +1,455 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view correctly filters nodes by name.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
var gPane = null;
var gTab = null;
var gDebugger = null;
var gDebuggee = null;
var gSearchBox = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
testSearchbox();
prepareVariables(testVariablesFiltering);
});
}
function testSearchbox()
{
ok(gDebugger.DebuggerView.Variables._searchboxNode,
"There should initially be a searchbox available in the variables view.");
ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
"There searchbox element should be found.");
gDebugger.DebuggerView.Variables.disableSearch();
ok(!gDebugger.DebuggerView.Variables._searchboxNode,
"There shouldn't be a searchbox available after disabling.");
ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
"There searchbox element should not be found.");
gDebugger.DebuggerView.Variables.enableSearch();
ok(gDebugger.DebuggerView.Variables._searchboxNode,
"There should be a searchbox available after enabling.");
ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
"There searchbox element should be found.");
let placeholder = "freshly squeezed mango juice";
gDebugger.DebuggerView.Variables.searchPlaceholder = placeholder;
ok(gDebugger.DebuggerView.Variables._searchboxNode.getAttribute("placeholder"),
placeholder, "There correct placeholder should be applied to the searchbox.");
gDebugger.DebuggerView.Variables.disableSearch();
ok(!gDebugger.DebuggerView.Variables._searchboxNode,
"There shouldn't be a searchbox available after disabling again.");
ok(!gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
"There searchbox element should not be found.");
gDebugger.DebuggerView.Variables.enableSearch();
ok(gDebugger.DebuggerView.Variables._searchboxNode,
"There should be a searchbox available after enabling again.");
ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
"There searchbox element should be found.");
ok(gDebugger.DebuggerView.Variables._searchboxNode.getAttribute("placeholder"),
placeholder, "There correct placeholder should be applied to the searchbox again.");
}
function testVariablesFiltering()
{
function test1()
{
write("location");
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, true,
"The mathScope expanded getter should return true");
is(testScopeItem.expanded, true,
"The testScope expanded getter should return true");
is(loadScopeItem.expanded, true,
"The loadScope expanded getter should return true");
is(globalScopeItem.expanded, true,
"The globalScope expanded getter should return true");
is(thisItem.expanded, true,
"The local scope 'this' should be expanded");
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded");
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded");
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded");
ignoreExtraMatchedProperties();
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the math scope");
is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the test scope");
is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the load scope");
is(globalScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the global scope");
is(innerScope.querySelectorAll(".property:not([non-match])").length, 5,
"There should be 5 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the test scope");
is(loadScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the load scope");
is(globalScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the global scope");
is(innerScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"this", "The only inner variable displayed should be 'this'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[0].getAttribute("value"),
"window", "The first inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[1].getAttribute("value"),
"document", "The second inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"location", "The third inner property displayed should be 'location'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"Location", "The fourth inner property displayed should be 'Location'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"Location", "The fifth inner property displayed should be 'Location'");
is(globalScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"Location", "The only global variable displayed should be 'Location'");
}
function test2()
{
innerScopeItem.collapse();
mathScopeItem.collapse();
testScopeItem.collapse();
loadScopeItem.collapse();
globalScopeItem.collapse();
thisItem.collapse();
windowItem.collapse();
documentItem.collapse();
locationItem.collapse();
is(innerScopeItem.expanded, false,
"The innerScope expanded getter should return false");
is(mathScopeItem.expanded, false,
"The mathScope expanded getter should return false");
is(testScopeItem.expanded, false,
"The testScope expanded getter should return false");
is(loadScopeItem.expanded, false,
"The loadScope expanded getter should return false");
is(globalScopeItem.expanded, false,
"The globalScope expanded getter should return false");
is(thisItem.expanded, false,
"The local scope 'this' should not be expanded");
is(windowItem.expanded, false,
"The local scope 'this.window' should not be expanded");
is(documentItem.expanded, false,
"The local scope 'this.window.document' should not be expanded");
is(locationItem.expanded, false,
"The local scope 'this.window.document.location' should not be expanded");
write("location");
is(thisItem.expanded, true,
"The local scope 'this' should be expanded");
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded");
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded");
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded");
ignoreExtraMatchedProperties();
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the math scope");
is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the test scope");
is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the load scope");
is(globalScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the global scope");
is(innerScope.querySelectorAll(".property:not([non-match])").length, 5,
"There should be 5 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the test scope");
is(loadScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the load scope");
is(globalScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the global scope");
is(innerScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"this", "The only inner variable displayed should be 'this'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[0].getAttribute("value"),
"window", "The first inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[1].getAttribute("value"),
"document", "The second inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"location", "The third inner property displayed should be 'location'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"Location", "The fourth inner property displayed should be 'Location'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"Location", "The fifth inner property displayed should be 'Location'");
is(globalScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"Location", "The only global variable displayed should be 'Location'");
}
var scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
let thisItem = innerScopeItem.get("this");
let windowItem = thisItem.get("window");
let documentItem = windowItem.get("document");
let locationItem = documentItem.get("location");
gSearchBox = gDebugger.DebuggerView.Variables._searchboxNode;
executeSoon(function() {
test1();
executeSoon(function() {
test2();
executeSoon(function() {
closeDebuggerAndFinish();
});
});
});
}
function prepareVariables(aCallback)
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 4 Debugger:FetchedVariables events, one from the global object
// scope, two from the |with| scopes and the regular one.
if (++count < 4) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._container._list,
scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, false,
"The mathScope expanded getter should return false");
is(testScopeItem.expanded, false,
"The testScope expanded getter should return false");
is(loadScopeItem.expanded, false,
"The loadScope expanded getter should return false");
is(globalScopeItem.expanded, false,
"The globalScope expanded getter should return false");
EventUtils.sendMouseEvent({ type: "mousedown" }, mathScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, testScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, loadScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, globalScope.querySelector(".arrow"), gDebuggee);
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, true,
"The mathScope expanded getter should return true");
is(testScopeItem.expanded, true,
"The testScope expanded getter should return true");
is(loadScopeItem.expanded, true,
"The loadScope expanded getter should return true");
is(globalScopeItem.expanded, true,
"The globalScope expanded getter should return true");
let thisItem = innerScopeItem.get("this");
is(thisItem.expanded, false,
"The local scope 'this' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test2() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test2, false);
Services.tm.currentThread.dispatch({ run: function() {
let windowItem = thisItem.get("window");
is(windowItem.expanded, false,
"The local scope 'this.window' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test3() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test3, false);
Services.tm.currentThread.dispatch({ run: function() {
let documentItem = windowItem.get("document");
is(documentItem.expanded, false,
"The local scope 'this.window.document' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test4() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test4, false);
Services.tm.currentThread.dispatch({ run: function() {
let locationItem = documentItem.get("location");
is(locationItem.expanded, false,
"The local scope 'this.window.document.location' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test5() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test5, false);
Services.tm.currentThread.dispatch({ run: function() {
is(thisItem.expanded, true,
"The local scope 'this' should be expanded");
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded");
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded");
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded");
executeSoon(function() {
aCallback();
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
locationItem.target.querySelector(".arrow"),
gDebuggee.window);
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded now");
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
documentItem.target.querySelector(".arrow"),
gDebuggee.window);
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded now");
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
windowItem.target.querySelector(".arrow"),
gDebuggee.window);
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded now");
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
thisItem.target.querySelector(".arrow"),
gDebuggee.window);
is(thisItem.expanded, true,
"The local scope 'this' should be expanded now");
});
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee.window);
}
function ignoreExtraMatchedProperties()
{
for (let [_, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
let name = item.name.toLowerCase();
if (name.contains("tracemallocdumpallocations") ||
name.contains("geolocation") ||
name.contains("webgl")) {
item.target.setAttribute("non-match", "");
}
}
}
function clear() {
gSearchBox.focus();
gSearchBox.value = "";
}
function write(text) {
clear();
append(text);
}
function append(text) {
gSearchBox.focus();
for (let i = 0; i < text.length; i++) {
EventUtils.sendChar(text[i]);
}
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
gDebuggee = null;
gSearchBox = null;
});

View File

@ -0,0 +1,399 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view correctly filters nodes by value.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
var gPane = null;
var gTab = null;
var gDebugger = null;
var gDebuggee = null;
var gSearchBox = null;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.contentWindow;
gDebuggee = aDebuggee;
testSearchbox();
prepareVariables(testVariablesFiltering);
});
}
function testSearchbox()
{
ok(gDebugger.DebuggerView.Variables._searchboxNode,
"There should initially be a searchbox available in the variables view.");
ok(gDebugger.DebuggerView.Variables._parent.querySelector(".devtools-searchinput"),
"There searchbox element should be found.");
}
function testVariablesFiltering()
{
function test1()
{
write("htmldocument");
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, true,
"The mathScope expanded getter should return true");
is(testScopeItem.expanded, true,
"The testScope expanded getter should return true");
is(loadScopeItem.expanded, true,
"The loadScope expanded getter should return true");
is(globalScopeItem.expanded, true,
"The globalScope expanded getter should return true");
is(thisItem.expanded, true,
"The local scope 'this' should be expanded");
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded");
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded");
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded");
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the math scope");
is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the test scope");
is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the load scope");
is(globalScope.querySelectorAll(".variable:not([non-match])").length, 2,
"There should be 2 variables displayed in the global scope");
is(innerScope.querySelectorAll(".property:not([non-match])").length, 5,
"There should be 5 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the test scope");
is(loadScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the load scope");
is(globalScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the global scope");
is(innerScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"this", "The only inner variable displayed should be 'this'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[0].getAttribute("value"),
"document", "The first inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[1].getAttribute("value"),
"window", "The second inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"document", "The third inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"HTMLDocument", "The fourth inner property displayed should be 'HTMLDocument'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"HTMLDocument", "The fifth inner property displayed should be 'HTMLDocument'");
is(globalScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"document", "The first global variable displayed should be 'document'");
is(globalScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[1].getAttribute("value"),
"HTMLDocument", "The first global variable displayed should be 'HTMLDocument'");
}
function test2()
{
innerScopeItem.collapse();
mathScopeItem.collapse();
testScopeItem.collapse();
loadScopeItem.collapse();
globalScopeItem.collapse();
thisItem.collapse();
windowItem.collapse();
documentItem.collapse();
locationItem.collapse();
is(innerScopeItem.expanded, false,
"The innerScope expanded getter should return false");
is(mathScopeItem.expanded, false,
"The mathScope expanded getter should return false");
is(testScopeItem.expanded, false,
"The testScope expanded getter should return false");
is(loadScopeItem.expanded, false,
"The loadScope expanded getter should return false");
is(globalScopeItem.expanded, false,
"The globalScope expanded getter should return false");
is(thisItem.expanded, false,
"The local scope 'this' should not be expanded");
is(windowItem.expanded, false,
"The local scope 'this.window' should not be expanded");
is(documentItem.expanded, false,
"The local scope 'this.window.document' should not be expanded");
is(locationItem.expanded, false,
"The local scope 'this.window.document.location' should not be expanded");
write("htmldocument");
is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
"There should be 1 variable displayed in the inner scope");
is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the math scope");
is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the test scope");
is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
"There should be 0 variables displayed in the load scope");
is(globalScope.querySelectorAll(".variable:not([non-match])").length, 2,
"There should be 2 variables displayed in the global scope");
is(innerScope.querySelectorAll(".property:not([non-match])").length, 5,
"There should be 5 properties displayed in the inner scope");
is(mathScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the math scope");
is(testScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the test scope");
is(loadScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the load scope");
is(globalScope.querySelectorAll(".property:not([non-match])").length, 0,
"There should be 0 properties displayed in the global scope");
is(innerScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"this", "The only inner variable displayed should be 'this'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[0].getAttribute("value"),
"document", "The first inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[1].getAttribute("value"),
"window", "The second inner property displayed should be 'window'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[2].getAttribute("value"),
"document", "The third inner property displayed should be 'document'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[3].getAttribute("value"),
"HTMLDocument", "The fourth inner property displayed should be 'HTMLDocument'");
is(innerScope.querySelectorAll(".property:not([non-match]) > .title > .name")[4].getAttribute("value"),
"HTMLDocument", "The fifth inner property displayed should be 'HTMLDocument'");
is(globalScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[0].getAttribute("value"),
"document", "The first global variable displayed should be 'document'");
is(globalScope.querySelectorAll(".variable:not([non-match]) > .title > .name")[1].getAttribute("value"),
"HTMLDocument", "The first global variable displayed should be 'HTMLDocument'");
}
var scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
let thisItem = innerScopeItem.get("this");
let windowItem = thisItem.get("window");
let documentItem = windowItem.get("document");
let locationItem = documentItem.get("location");
gSearchBox = gDebugger.DebuggerView.Variables._searchboxNode;
executeSoon(function() {
test1();
executeSoon(function() {
test2();
executeSoon(function() {
closeDebuggerAndFinish();
});
});
});
}
function prepareVariables(aCallback)
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 4 Debugger:FetchedVariables events, one from the global object
// scope, two from the |with| scopes and the regular one.
if (++count < 4) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
var frames = gDebugger.DebuggerView.StackFrames._container._list,
scopes = gDebugger.DebuggerView.Variables._list,
innerScope = scopes.querySelectorAll(".scope")[0],
mathScope = scopes.querySelectorAll(".scope")[1],
testScope = scopes.querySelectorAll(".scope")[2],
loadScope = scopes.querySelectorAll(".scope")[3],
globalScope = scopes.querySelectorAll(".scope")[4];
let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
innerScope.querySelector(".name").getAttribute("value"));
let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
mathScope.querySelector(".name").getAttribute("value"));
let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
testScope.querySelector(".name").getAttribute("value"));
let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
loadScope.querySelector(".name").getAttribute("value"));
let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
globalScope.querySelector(".name").getAttribute("value"));
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, false,
"The mathScope expanded getter should return false");
is(testScopeItem.expanded, false,
"The testScope expanded getter should return false");
is(loadScopeItem.expanded, false,
"The loadScope expanded getter should return false");
is(globalScopeItem.expanded, false,
"The globalScope expanded getter should return false");
EventUtils.sendMouseEvent({ type: "mousedown" }, mathScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, testScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, loadScope.querySelector(".arrow"), gDebuggee);
EventUtils.sendMouseEvent({ type: "mousedown" }, globalScope.querySelector(".arrow"), gDebuggee);
is(innerScopeItem.expanded, true,
"The innerScope expanded getter should return true");
is(mathScopeItem.expanded, true,
"The mathScope expanded getter should return true");
is(testScopeItem.expanded, true,
"The testScope expanded getter should return true");
is(loadScopeItem.expanded, true,
"The loadScope expanded getter should return true");
is(globalScopeItem.expanded, true,
"The globalScope expanded getter should return true");
let thisItem = innerScopeItem.get("this");
is(thisItem.expanded, false,
"The local scope 'this' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test2() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test2, false);
Services.tm.currentThread.dispatch({ run: function() {
let windowItem = thisItem.get("window");
is(windowItem.expanded, false,
"The local scope 'this.window' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test3() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test3, false);
Services.tm.currentThread.dispatch({ run: function() {
let documentItem = windowItem.get("document");
is(documentItem.expanded, false,
"The local scope 'this.window.document' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test4() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test4, false);
Services.tm.currentThread.dispatch({ run: function() {
let locationItem = documentItem.get("location");
is(locationItem.expanded, false,
"The local scope 'this.window.document.location' should not be expanded yet");
gDebugger.addEventListener("Debugger:FetchedProperties", function test5() {
gDebugger.removeEventListener("Debugger:FetchedProperties", test5, false);
Services.tm.currentThread.dispatch({ run: function() {
is(thisItem.expanded, true,
"The local scope 'this' should be expanded");
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded");
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded");
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded");
executeSoon(function() {
aCallback();
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
locationItem.target.querySelector(".arrow"),
gDebuggee.window);
is(locationItem.expanded, true,
"The local scope 'this.window.document.location' should be expanded now");
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
documentItem.target.querySelector(".arrow"),
gDebuggee.window);
is(documentItem.expanded, true,
"The local scope 'this.window.document' should be expanded now");
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
windowItem.target.querySelector(".arrow"),
gDebuggee.window);
is(windowItem.expanded, true,
"The local scope 'this.window' should be expanded now");
});
}}, 0);
}, false);
executeSoon(function() {
EventUtils.sendMouseEvent({ type: "mousedown" },
thisItem.target.querySelector(".arrow"),
gDebuggee.window);
is(thisItem.expanded, true,
"The local scope 'this' should be expanded now");
});
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee.window);
}
function clear() {
gSearchBox.focus();
gSearchBox.value = "";
}
function write(text) {
clear();
append(text);
}
function append(text) {
gSearchBox.focus();
for (let i = 0; i < text.length; i++) {
EventUtils.sendChar(text[i]);
}
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
gDebuggee = null;
gSearchBox = null;
});

View File

@ -103,10 +103,15 @@ noMatchingStringsText=No matches found
# filter text box when it is empty and the scripts container is selected.
emptyFilterText=Filter scripts (%S)
# LOCALIZATION NOTE (emptyFilterText): This is the text that appears in the
# filter text box when it is empty and the chrome globals container is selected.
# LOCALIZATION NOTE (emptyChromeGlobalsFilterText): This is the text that
# appears in the filter text box when it is empty and the chrome globals
# container is selected.
emptyChromeGlobalsFilterText=Filter chrome globals (%S)
# LOCALIZATION NOTE (emptyVariablesFilterText): This is the text that
# appears in the filter text box for the variables view container.
emptyVariablesFilterText=Filter variables
# LOCALIZATION NOTE (searchPanelGlobal): This is the text that appears in the
# filter panel popup for the global search operation.
searchPanelGlobal=Search in all files (%S)

View File

@ -319,6 +319,20 @@
text-decoration: line-through;
}
/**
* Variables and properties searching
*/
#variables .devtools-searchinput {
min-height: 24px;
}
.variable[non-match],
.property[non-match] {
border: none;
margin: 0;
}
/**
* Token value colors
*/

View File

@ -321,6 +321,20 @@
text-decoration: line-through;
}
/**
* Variables and properties searching
*/
#variables .devtools-searchinput {
min-height: 24px;
}
.variable[non-match],
.property[non-match] {
border: none;
margin: 0;
}
/**
* Token value colors
*/

View File

@ -327,6 +327,20 @@
text-decoration: line-through;
}
/**
* Variables and properties searching
*/
#variables .devtools-searchinput {
min-height: 24px;
}
.variable[non-match],
.property[non-match] {
border: none;
margin: 0;
}
/**
* Token value colors
*/