From 21fe763a1d62cd81c57b279954627e14765e83cd Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Sun, 4 Nov 2012 01:01:05 +0200 Subject: [PATCH 01/10] Bug 798874 - The variables view should be filterable, r=past --- browser/devtools/debugger/VariablesView.jsm | 161 ++++++- browser/devtools/debugger/debugger-panes.js | 9 +- browser/devtools/debugger/debugger-view.js | 2 + browser/devtools/debugger/debugger.css | 9 + browser/devtools/debugger/test/Makefile.in | 2 + .../browser_dbg_propertyview-filter-01.js | 455 ++++++++++++++++++ .../browser_dbg_propertyview-filter-02.js | 399 +++++++++++++++ .../browser/devtools/debugger.properties | 9 +- .../themes/gnomestripe/devtools/debugger.css | 14 + .../themes/pinstripe/devtools/debugger.css | 14 + .../themes/winstripe/devtools/debugger.css | 14 + 11 files changed, 1079 insertions(+), 9 deletions(-) create mode 100644 browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js create mode 100644 browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js diff --git a/browser/devtools/debugger/VariablesView.jsm b/browser/devtools/debugger/VariablesView.jsm index cc50de2eb35..88bb02a47f5 100644 --- a/browser/devtools/debugger/VariablesView.jsm +++ b/browser/devtools/debugger/VariablesView.jsm @@ -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, diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js index ddb6e260b07..6de7b2292f9 100644 --- a/browser/devtools/debugger/debugger-panes.js +++ b/browser/devtools/debugger/debugger-panes.js @@ -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 }; /** diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index f0ec5b1ebdb..c7771e2ce73 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -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; diff --git a/browser/devtools/debugger/debugger.css b/browser/devtools/debugger/debugger.css index ef8071e3dc6..3f434ac569f 100644 --- a/browser/devtools/debugger/debugger.css +++ b/browser/devtools/debugger/debugger.css @@ -63,6 +63,15 @@ display: -moz-box; } +/** + * Variables and properties searching + */ + +.variable[non-match] > .title, +.property[non-match] > .title { + display: none; +} + /** * Toolbar */ diff --git a/browser/devtools/debugger/test/Makefile.in b/browser/devtools/debugger/test/Makefile.in index ae887073e16..9d0d774807d 100644 --- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -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 \ diff --git a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js new file mode 100644 index 00000000000..4257e1f9b29 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js @@ -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; +}); diff --git a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js new file mode 100644 index 00000000000..13c8e054034 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js @@ -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; +}); diff --git a/browser/locales/en-US/chrome/browser/devtools/debugger.properties b/browser/locales/en-US/chrome/browser/devtools/debugger.properties index 3b1fe1df141..327e4d7fcfb 100644 --- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties +++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties @@ -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) diff --git a/browser/themes/gnomestripe/devtools/debugger.css b/browser/themes/gnomestripe/devtools/debugger.css index 9436d80df4d..b913d1f62e5 100644 --- a/browser/themes/gnomestripe/devtools/debugger.css +++ b/browser/themes/gnomestripe/devtools/debugger.css @@ -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 */ diff --git a/browser/themes/pinstripe/devtools/debugger.css b/browser/themes/pinstripe/devtools/debugger.css index b44f5df007c..b3d5decdf33 100644 --- a/browser/themes/pinstripe/devtools/debugger.css +++ b/browser/themes/pinstripe/devtools/debugger.css @@ -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 */ diff --git a/browser/themes/winstripe/devtools/debugger.css b/browser/themes/winstripe/devtools/debugger.css index e3d3f887d39..4e7b30f24b5 100644 --- a/browser/themes/winstripe/devtools/debugger.css +++ b/browser/themes/winstripe/devtools/debugger.css @@ -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 */ From ce3c4d522a66fe30b1e051713b914eb4189f08d6 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Sun, 4 Nov 2012 01:01:05 +0200 Subject: [PATCH 02/10] Bug 793375 - Search operator for finding a variable in scope(s) while the debugger is paused, r=past --- browser/app/profile/firefox.js | 3 +- browser/devtools/debugger/VariablesView.jsm | 82 ++++- .../devtools/debugger/debugger-controller.js | 37 ++- browser/devtools/debugger/debugger-toolbar.js | 112 +++++-- browser/devtools/debugger/debugger-view.js | 5 +- browser/devtools/debugger/debugger.xul | 25 +- browser/devtools/debugger/test/Makefile.in | 3 + .../browser_dbg_bug786070_hide_nonenums.js | 16 +- .../browser_dbg_propertyview-filter-01.js | 48 ++- .../browser_dbg_propertyview-filter-02.js | 36 ++- .../browser_dbg_propertyview-filter-03.js | 93 ++++++ .../browser_dbg_propertyview-filter-04.js | 93 ++++++ .../browser_dbg_propertyview-filter-05.js | 303 ++++++++++++++++++ .../chrome/browser/devtools/debugger.dtd | 5 + .../browser/devtools/debugger.properties | 4 + 15 files changed, 781 insertions(+), 84 deletions(-) create mode 100644 browser/devtools/debugger/test/browser_dbg_propertyview-filter-03.js create mode 100644 browser/devtools/debugger/test/browser_dbg_propertyview-filter-04.js create mode 100644 browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 5f48b21cc04..7068d31f3ea 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1040,7 +1040,8 @@ pref("devtools.debugger.ui.remote-win.height", 400); pref("devtools.debugger.ui.stackframes-width", 200); pref("devtools.debugger.ui.variables-width", 300); pref("devtools.debugger.ui.panes-visible-on-startup", false); -pref("devtools.debugger.ui.non-enum-visible", true); +pref("devtools.debugger.ui.variables-non-enum-visible", true); +pref("devtools.debugger.ui.variables-searchbox-visible", false); // Enable the style inspector pref("devtools.styleinspector.enabled", true); diff --git a/browser/devtools/debugger/VariablesView.jsm b/browser/devtools/debugger/VariablesView.jsm index 88bb02a47f5..468d132d7bb 100644 --- a/browser/devtools/debugger/VariablesView.jsm +++ b/browser/devtools/debugger/VariablesView.jsm @@ -185,6 +185,16 @@ VariablesView.prototype = { this._searchboxNode = null; }, + /** + * Sets if the variable and property searching is enabled. + */ + set searchEnabled(aFlag) aFlag ? this.enableSearch() : this.disableSearch(), + + /** + * Gets if the variable and property searching is enabled. + */ + get searchEnabled() !!this._searchboxContainer, + /** * Performs a case insensitive search for variables or properties matching * the query, and hides non-matched items. @@ -193,10 +203,28 @@ VariablesView.prototype = { * The variable or property to search for. */ performSearch: function VV_performSerch(aQuery) { - let lowerCaseQuery = aQuery.toLowerCase(); + if (!aQuery) { + for (let [_, item] of this._currHierarchy) { + item._match = true; + } + } else { + for (let [_, scope] in this) { + scope._performSearch(aQuery.toLowerCase()); + } + } + }, + /** + * Expands the first search results in this container. + */ + expandFirstSearchResults: function VV_expandFirstSearchResults() { for (let [_, scope] in this) { - scope._performSearch(lowerCaseQuery); + for (let [_, variable] in scope) { + if (variable._isMatch) { + variable.expand(); + break; + } + } } }, @@ -406,7 +434,7 @@ Scope.prototype = { * Pass true to not show an opening animation. */ expand: function S_expand(aSkipAnimationFlag) { - if (this._locked) { + if (this._isExpanded || this._locked) { return; } if (this._variablesView._enumVisible) { @@ -431,7 +459,7 @@ Scope.prototype = { * Collapses the scope, hiding all the added details. */ collapse: function S_collapse() { - if (this._locked) { + if (!this._isExpanded || this._locked) { return; } this._arrow.removeAttribute("open"); @@ -486,12 +514,6 @@ 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 @@ -651,17 +673,17 @@ Scope.prototype = { // Non-matched variables or properties require a corresponding attribute. if (!lowerCaseName.contains(aLowerCaseQuery) && !lowerCaseValue.contains(aLowerCaseQuery)) { - variable.target.setAttribute("non-match", ""); + variable._match = false; } // Variable or property is matched. else { - variable.target.removeAttribute("non-match"); + variable._match = true; // 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) { + if (variable._wasToggled) { variable.expand(true); } @@ -675,13 +697,32 @@ Scope.prototype = { variable instanceof Property)) { // Show and expand the parent, as it is certainly accessible. - variable.target.removeAttribute("non-match"); + variable._match = true; variable.expand(true); } } // Proceed with the search recursively inside this variable or property. - currentObject._performSearch(aLowerCaseQuery); + if (variable._wasToggled || variable.expanded || variable.getter || variable.setter) { + currentObject._performSearch(aLowerCaseQuery); + } + } + }, + + /** + * Sets if this object instance is a match or non-match. + * @param boolean aStatus + */ + set _match(aStatus) { + if (this._isMatch == aStatus) { + return; + } + if (aStatus) { + this._isMatch = true; + this.target.removeAttribute("non-match"); + } else { + this._isMatch = false; + this.target.setAttribute("non-match", ""); } }, @@ -725,6 +766,7 @@ Scope.prototype = { _isExpanded: false, _wasToggled: false, _isArrowVisible: true, + _isMatch: true, _store: null, _idString: "", _nameString: "", @@ -818,6 +860,16 @@ create({ constructor: Variable, proto: Scope.prototype }, { } }, + /** + * Returns this variable's getter from the descriptor if available, + */ + get getter() this._initialDescriptor.get, + + /** + * Returns this variable's getter from the descriptor if available, + */ + get setter() this._initialDescriptor.set, + /** * Sets the specific grip for this variable. * The grip should contain the value or the type & class, as defined in the diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js index 7080aff3dfd..d9ee09e36c6 100644 --- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -1248,7 +1248,8 @@ XPCOMUtils.defineLazyGetter(L10N, "ellipsis", function() { const STACKFRAMES_WIDTH = "devtools.debugger.ui.stackframes-width"; const VARIABLES_WIDTH = "devtools.debugger.ui.variables-width"; const PANES_VISIBLE_ON_STARTUP = "devtools.debugger.ui.panes-visible-on-startup"; -const NON_ENUM_VISIBLE = "devtools.debugger.ui.non-enum-visible"; +const VARIABLES_NON_ENUM_VISIBLE = "devtools.debugger.ui.variables-non-enum-visible"; +const VARIABLES_SEARCHBOX_VISIBLE = "devtools.debugger.ui.variables-searchbox-visible"; const REMOTE_HOST = "devtools.debugger.remote-host"; const REMOTE_PORT = "devtools.debugger.remote-port"; const REMOTE_AUTO_CONNECT = "devtools.debugger.remote-autoconnect"; @@ -1324,11 +1325,11 @@ let Prefs = { * properties and variables in the scope view. * @return boolean */ - get nonEnumVisible() { - if (this._nonEnumVisible === undefined) { - this._nonEnumVisible = Services.prefs.getBoolPref(NON_ENUM_VISIBLE); + get variablesNonEnumVisible() { + if (this._varNonEnum === undefined) { + this._varNonEnum = Services.prefs.getBoolPref(VARIABLES_NON_ENUM_VISIBLE); } - return this._nonEnumVisible; + return this._varNonEnum; }, /** @@ -1336,9 +1337,29 @@ let Prefs = { * properties and variables in the scope view. * @param boolean value */ - set nonEnumVisible(value) { - Services.prefs.setBoolPref(NON_ENUM_VISIBLE, value); - this._nonEnumVisible = value; + set variablesNonEnumVisible(value) { + Services.prefs.setBoolPref(VARIABLES_NON_ENUM_VISIBLE, value); + this._varNonEnum = value; + }, + + /** + * Gets a flag specifying if the a variables searchbox should be shown. + * @return boolean + */ + get variablesSearchboxVisible() { + if (this._varSearchbox === undefined) { + this._varSearchbox = Services.prefs.getBoolPref(VARIABLES_SEARCHBOX_VISIBLE); + } + return this._varSearchbox; + }, + + /** + * Sets a flag specifying if the a variables searchbox should be shown. + * @param boolean value + */ + set variablesSearchboxVisible(value) { + Services.prefs.setBoolPref(VARIABLES_SEARCHBOX_VISIBLE, value); + this._varSearchbox = value; }, /** diff --git a/browser/devtools/debugger/debugger-toolbar.js b/browser/devtools/debugger/debugger-toolbar.js index 745d3380232..988ec3c117a 100644 --- a/browser/devtools/debugger/debugger-toolbar.js +++ b/browser/devtools/debugger/debugger-toolbar.js @@ -199,7 +199,8 @@ function OptionsView() { dumpn("OptionsView was instantiated"); this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this); this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this); - this._toggleShowNonEnum = this._toggleShowNonEnum.bind(this); + this._toggleShowVariablesNonEnum = this._toggleShowVariablesNonEnum.bind(this); + this._toggleShowVariablesSearchbox = this._toggleShowVariablesSearchbox.bind(this); } OptionsView.prototype = { @@ -211,11 +212,13 @@ OptionsView.prototype = { this._button = document.getElementById("debugger-options"); this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions"); this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup"); - this._showNonEnumItem = document.getElementById("show-nonenum"); + this._showVariablesNonEnumItem = document.getElementById("show-vars-nonenum"); + this._showVariablesSearchboxItem = document.getElementById("show-vars-searchbox"); this._pauseOnExceptionsItem.setAttribute("checked", "false"); this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup); - this._showNonEnumItem.setAttribute("checked", Prefs.nonEnumVisible); + this._showVariablesNonEnumItem.setAttribute("checked", Prefs.variablesNonEnumVisible); + this._showVariablesSearchboxItem.setAttribute("checked", Prefs.variablesSearchboxVisible); }, /** @@ -259,15 +262,24 @@ OptionsView.prototype = { /** * Listener handling the 'show non-enumerables' menuitem command. */ - _toggleShowNonEnum: function DVO__toggleShowNonEnum() { - DebuggerView.Variables.nonEnumVisible = Prefs.nonEnumVisible = - this._showNonEnumItem.getAttribute("checked") == "true"; + _toggleShowVariablesNonEnum: function DVO__toggleShowVariablesNonEnum() { + DebuggerView.Variables.nonEnumVisible = Prefs.variablesNonEnumVisible = + this._showVariablesNonEnumItem.getAttribute("checked") == "true"; + }, + + /** + * Listener handling the 'show variables searchbox' menuitem command. + */ + _toggleShowVariablesSearchbox: function DVO__toggleShowVariablesSearchbox() { + DebuggerView.Variables.searchEnabled = Prefs.variablesSearchboxVisible = + this._showVariablesSearchboxItem.getAttribute("checked") == "true"; }, _button: null, _pauseOnExceptionsItem: null, _showPanesOnStartupItem: null, - _showNonEnumItem: null + _showVariablesNonEnumItem: null, + _showVariablesSearchboxItem: null }; /** @@ -562,11 +574,14 @@ FilterView.prototype = { this._tokenOperatorLabel = document.getElementById("token-operator-label"); this._lineOperatorButton = document.getElementById("line-operator-button"); this._lineOperatorLabel = document.getElementById("line-operator-label"); + this._variableOperatorButton = document.getElementById("variable-operator-button"); + this._variableOperatorLabel = document.getElementById("variable-operator-label"); this._globalSearchKey = LayoutHelpers.prettyKey(document.getElementById("globalSearchKey")); this._fileSearchKey = LayoutHelpers.prettyKey(document.getElementById("fileSearchKey")); this._lineSearchKey = LayoutHelpers.prettyKey(document.getElementById("lineSearchKey")); this._tokenSearchKey = LayoutHelpers.prettyKey(document.getElementById("tokenSearchKey")); + this._variableSearchKey = LayoutHelpers.prettyKey(document.getElementById("variableSearchKey")); this._searchbox.addEventListener("click", this._onClick, false); this._searchbox.addEventListener("select", this._onSearch, false); @@ -577,6 +592,7 @@ FilterView.prototype = { this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG); this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG); this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG); + this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG); this._globalOperatorLabel.setAttribute("value", L10N.getFormatStr("searchPanelGlobal", [this._globalSearchKey])); @@ -584,6 +600,8 @@ FilterView.prototype = { L10N.getFormatStr("searchPanelToken", [this._tokenSearchKey])); this._lineOperatorLabel.setAttribute("value", L10N.getFormatStr("searchPanelLine", [this._lineSearchKey])); + this._variableOperatorLabel.setAttribute("value", + L10N.getFormatStr("searchPanelVariable", [this._variableSearchKey])); // TODO: bug 806775 // if (window._isChromeDebugger) { @@ -628,16 +646,17 @@ FilterView.prototype = { * @return array */ get searchboxInfo() { - let file, line, token, global; + let file, line, token, isGlobal, isVariable; let rawValue = this._searchbox.value; let rawLength = rawValue.length; let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG); + let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG); let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG); let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG); - // This is not a global search, allow file or line flags. - if (globalFlagIndex != 0) { + // This is not a global or variable search, allow file or line flags. + if (globalFlagIndex != 0 && variableFlagIndex != 0) { let fileEnd = lineFlagIndex != -1 ? lineFlagIndex : tokenFlagIndex != -1 ? tokenFlagIndex : rawLength; @@ -649,17 +668,27 @@ FilterView.prototype = { file = rawValue.slice(0, fileEnd); line = ~~(rawValue.slice(fileEnd + 1, lineEnd)) || -1; token = rawValue.slice(lineEnd + 1); - global = false; + isGlobal = false; + isVariable = false; } // Global searches dissalow the use of file or line flags. - else { + else if (globalFlagIndex == 0) { file = ""; line = -1; token = rawValue.slice(1); - global = true; + isGlobal = true; + isVariable = false; + } + // Variable searches dissalow the use of file or line flags. + else if (variableFlagIndex == 0) { + file = ""; + line = -1; + token = rawValue.slice(1); + isGlobal = false; + isVariable = true; } - return [file, line, token, global]; + return [file, line, token, isGlobal, isVariable]; }, /** @@ -787,25 +816,33 @@ FilterView.prototype = { */ _onSearch: function DVF__onScriptsSearch() { this._searchboxPanel.hidePopup(); - let [file, line, token, global] = this.searchboxInfo; + let [file, line, token, isGlobal, isVariable] = this.searchboxInfo; // If this is a global search, schedule it for when the user stops typing, // or hide the corresponding pane otherwise. - if (global) { + if (isGlobal) { DebuggerView.GlobalSearch.scheduleSearch(); - } else { - DebuggerView.GlobalSearch.clearView(); - this._performFileSearch(file); - this._performLineSearch(line); - this._performTokenSearch(token); + return; } + + // If this is a variable search, defer the action to the corresponding + // variables view instance. + if (isVariable) { + DebuggerView.Variables.performSearch(token); + return; + } + + DebuggerView.GlobalSearch.clearView(); + this._performFileSearch(file); + this._performLineSearch(line); + this._performTokenSearch(token); }, /** * The key press listener for the search container. */ _onKeyPress: function DVF__onScriptsKeyPress(e) { - let [file, line, token, global] = this.searchboxInfo; + let [file, line, token, isGlobal, isVariable] = this.searchboxInfo; let action; switch (e.keyCode) { @@ -835,18 +872,26 @@ FilterView.prototype = { e.preventDefault(); e.stopPropagation(); - if (global) { + // Perform a global search based on the specified operator. + if (isGlobal) { if (DebuggerView.GlobalSearch.hidden) { DebuggerView.GlobalSearch.scheduleSearch(); } else { DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]](); } - } else { - let editor = DebuggerView.editor; - let offset = editor[["findNext", "findPrevious"][action]](true); - if (offset > -1) { - editor.setSelection(offset, offset + token.length) - } + return; + } + + // Perform a variable search based on the specified operator. + if (isVariable) { + DebuggerView.Variables.expandFirstSearchResults(); + return; + } + + let editor = DebuggerView.editor; + let offset = editor[["findNext", "findPrevious"][action]](true); + if (offset > -1) { + editor.setSelection(offset, offset + token.length) } }, @@ -855,6 +900,7 @@ FilterView.prototype = { */ _onBlur: function DVF__onBlur() { DebuggerView.GlobalSearch.clearView(); + DebuggerView.Variables.performSearch(null); this._searchboxPanel.hidePopup(); }, @@ -902,6 +948,14 @@ FilterView.prototype = { this._searchboxPanel.hidePopup(); }, + /** + * Called when the variable search filter key sequence was pressed. + */ + _doVariableSearch: function DVF__doVariableSearch() { + this._doSearch(SEARCH_VARIABLE_FLAG); + this._searchboxPanel.hidePopup(); + }, + _searchbox: null, _searchboxPanel: null, _globalOperatorButton: null, diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index c7771e2ce73..fae7111cac9 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -15,6 +15,7 @@ const GLOBAL_SEARCH_ACTION_DELAY = 150; // ms const SEARCH_GLOBAL_FLAG = "!"; const SEARCH_LINE_FLAG = ":"; const SEARCH_TOKEN_FLAG = "#"; +const SEARCH_VARIABLE_FLAG = "*"; /** * Object defining the debugger view components. @@ -38,10 +39,10 @@ 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.nonEnumVisible = Prefs.variablesNonEnumVisible; + this.Variables.searchEnabled = Prefs.variablesSearchboxVisible; this.Variables.eval = DebuggerController.StackFrames.evaluate; this.Variables.lazyEmpty = true; diff --git a/browser/devtools/debugger/debugger.xul b/browser/devtools/debugger/debugger.xul index 67b30d69c64..c49af11341c 100644 --- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -41,12 +41,16 @@ oncommand="DebuggerView.Filtering._doTokenSearch()"/> + + oncommand="DebuggerView.Options._toggleShowVariablesNonEnum()"/> + @@ -75,11 +79,16 @@ label="&debuggerUI.showPanesOnInit;" accesskey="&debuggerUI.showPanesOnInit.key;" command="toggleShowPanesOnStartup"/> - + @@ -115,6 +124,10 @@ key="F" modifiers="control shift" command="globalSearchCommand"/> + @@ -158,6 +171,7 @@ tooltiptext="&debuggerUI.closeButton.tooltip;"/> #endif +