From f27c6aaf173faa5f4a67d7c3d7af4a955aeab5ad Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Sun, 4 Nov 2012 01:01:05 +0200 Subject: [PATCH] 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 5f48b21cc04a..7068d31f3ea9 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 88bb02a47f5d..468d132d7bb4 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 7080aff3dfd0..d9ee09e36c6e 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 745d33802321..988ec3c117a2 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 c7771e2ce730..fae7111cac99 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 67b30d69c647..c49af11341c7 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 +