Bug 813587 - When performing global searches with very few characters the browser may become unresponsive, r=past

This commit is contained in:
Victor Porof 2012-11-21 10:49:45 +02:00
parent 5dd997da61
commit e88eef8daf
10 changed files with 106 additions and 20 deletions

View File

@ -187,9 +187,9 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false); window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
this._container.addEventListener("click", this._onBreakpointClick, false); this._container.addEventListener("click", this._onBreakpointClick, false);
this._cmPopup.addEventListener("popuphidden", this._onEditorContextMenuPopupHidden, false); this._cmPopup.addEventListener("popuphidden", this._onEditorContextMenuPopupHidden, false);
this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false) this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false) this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false) this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false); this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
this._cache = new Map(); this._cache = new Map();
@ -206,7 +206,7 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
this._cmPopup.removeEventListener("popuphidden", this._onEditorContextMenuPopupHidden, false); this._cmPopup.removeEventListener("popuphidden", this._onEditorContextMenuPopupHidden, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false); this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.removeEventListener("popupshown", this._onConditionalPopupShown, false); this._cbPanel.removeEventListener("popupshown", this._onConditionalPopupShown, false);
this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false) this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false); this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
this._cbPanel.hidePopup(); this._cbPanel.hidePopup();
@ -645,7 +645,7 @@ create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
}, },
/** /**
* The context menu popup hiding listener for the source editor. * The context menu popup hidden listener for the source editor.
*/ */
_onEditorContextMenuPopupHidden: function DVB__onEditorContextMenuPopupHidden() { _onEditorContextMenuPopupHidden: function DVB__onEditorContextMenuPopupHidden() {
this._editorContextMenuLineNumber = -1; this._editorContextMenuLineNumber = -1;
@ -1285,19 +1285,50 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
}, },
/** /**
* Schedules searching for a string in all of the sources. * Allows searches to be scheduled and delayed to avoid redundant calls.
*/ */
scheduleSearch: function DVGS_scheduleSearch() { delayedSearch: true,
/**
* Schedules searching for a string in all of the sources.
*
* @param string aQuery
* The string to search for.
*/
scheduleSearch: function DVGS_scheduleSearch(aQuery) {
if (!this.delayedSearch) {
this.performSearch(aQuery);
return;
}
let delay = Math.max(GLOBAL_SEARCH_ACTION_MAX_DELAY / aQuery.length);
window.clearTimeout(this._searchTimeout); window.clearTimeout(this._searchTimeout);
this._searchTimeout = window.setTimeout(this._startSearch, GLOBAL_SEARCH_ACTION_DELAY); this._searchFunction = this._startSearch.bind(this, aQuery);
this._searchTimeout = window.setTimeout(this._searchFunction, delay);
},
/**
* Immediately searches for a string in all of the sources.
*
* @param string aQuery
* The string to search for.
*/
performSearch: function DVGS_performSearch(aQuery) {
window.clearTimeout(this._searchTimeout);
this._searchFunction = null;
this._startSearch(aQuery);
}, },
/** /**
* Starts searching for a string in all of the sources. * Starts searching for a string in all of the sources.
*
* @param string aQuery
* The string to search for.
*/ */
_startSearch: function DVGS__startSearch() { _startSearch: function DVGS__startSearch(aQuery) {
let locations = DebuggerView.Sources.values; let locations = DebuggerView.Sources.values;
this._sourcesCount = locations.length; this._sourcesCount = locations.length;
this._searchedToken = aQuery;
this._fetchSources( this._fetchSources(
this._onFetchSourceFinished, this._onFetchSourceFinished,
@ -1363,7 +1394,7 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
*/ */
_performGlobalSearch: function DVGS__performGlobalSearch() { _performGlobalSearch: function DVGS__performGlobalSearch() {
// Get the currently searched token from the filtering input. // Get the currently searched token from the filtering input.
let token = DebuggerView.Filtering.searchedToken; let token = this._searchedToken;
// Make sure we're actually searching for something. // Make sure we're actually searching for something.
if (!token) { if (!token) {
@ -1625,6 +1656,8 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
_currentlyFocusedMatch: -1, _currentlyFocusedMatch: -1,
_forceExpandResults: false, _forceExpandResults: false,
_searchTimeout: null, _searchTimeout: null,
_searchFunction: null,
_searchedToken: "",
_sourcesCount: -1, _sourcesCount: -1,
_cache: null _cache: null
}); });

View File

@ -821,14 +821,14 @@ FilterView.prototype = {
// If this is a global search, schedule it for when the user stops typing, // If this is a global search, schedule it for when the user stops typing,
// or hide the corresponding pane otherwise. // or hide the corresponding pane otherwise.
if (isGlobal) { if (isGlobal) {
DebuggerView.GlobalSearch.scheduleSearch(); DebuggerView.GlobalSearch.scheduleSearch(token);
return; return;
} }
// If this is a variable search, defer the action to the corresponding // If this is a variable search, defer the action to the corresponding
// variables view instance. // variables view instance.
if (isVariable) { if (isVariable) {
DebuggerView.Variables.performSearch(token); DebuggerView.Variables.scheduleSearch(token);
return; return;
} }
@ -843,12 +843,17 @@ FilterView.prototype = {
*/ */
_onKeyPress: function DVF__onScriptsKeyPress(e) { _onKeyPress: function DVF__onScriptsKeyPress(e) {
let [file, line, token, isGlobal, isVariable] = this.searchboxInfo; let [file, line, token, isGlobal, isVariable] = this.searchboxInfo;
let action; let isDifferentToken, isReturnKey, action;
if (this._prevSearchedToken != token) {
isDifferentToken = true;
}
switch (e.keyCode) { switch (e.keyCode) {
case e.DOM_VK_DOWN:
case e.DOM_VK_RETURN: case e.DOM_VK_RETURN:
case e.DOM_VK_ENTER: case e.DOM_VK_ENTER:
isReturnKey = true;
// fall through
case e.DOM_VK_DOWN:
action = 0; action = 0;
break; break;
case e.DOM_VK_UP: case e.DOM_VK_UP:
@ -874,18 +879,22 @@ FilterView.prototype = {
// Perform a global search based on the specified operator. // Perform a global search based on the specified operator.
if (isGlobal) { if (isGlobal) {
if (DebuggerView.GlobalSearch.hidden) { if (isReturnKey && isDifferentToken) {
DebuggerView.GlobalSearch.scheduleSearch(); DebuggerView.GlobalSearch.performSearch(token);
} else { } else {
DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]](); DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]]();
} }
this._prevSearchedToken = token;
return; return;
} }
// Perform a variable search based on the specified operator. // Perform a variable search based on the specified operator.
if (isVariable) { if (isVariable) {
DebuggerView.Variables.performSearch(token); if (isReturnKey && isDifferentToken) {
DebuggerView.Variables.expandFirstSearchResults(); DebuggerView.Variables.performSearch(token);
DebuggerView.Variables.expandFirstSearchResults();
}
this._prevSearchedToken = token;
return; return;
} }

View File

@ -13,7 +13,7 @@ const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "after_start";
const BREAKPOINT_CONDITIONAL_POPUP_OFFSET = 50; // px const BREAKPOINT_CONDITIONAL_POPUP_OFFSET = 50; // px
const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50; const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
const GLOBAL_SEARCH_ACTION_DELAY = 150; // ms const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
const SEARCH_GLOBAL_FLAG = "!"; const SEARCH_GLOBAL_FLAG = "!";
const SEARCH_LINE_FLAG = ":"; const SEARCH_LINE_FLAG = ":";
const SEARCH_TOKEN_FLAG = "#"; const SEARCH_TOKEN_FLAG = "#";

View File

@ -25,6 +25,7 @@ function test()
gDebuggee = aDebuggee; gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true; gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.delayedSearch = false;
testSearchbox(); testSearchbox();
prepareVariables(testVariablesFiltering); prepareVariables(testVariablesFiltering);
}); });

View File

@ -25,6 +25,7 @@ function test()
gDebuggee = aDebuggee; gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true; gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.delayedSearch = false;
testSearchbox(); testSearchbox();
prepareVariables(testVariablesFiltering); prepareVariables(testVariablesFiltering);
}); });

View File

@ -25,6 +25,7 @@ function test()
gDebuggee = aDebuggee; gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true; gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.delayedSearch = false;
prepareVariables(testVariablesFiltering); prepareVariables(testVariablesFiltering);
}); });
} }

View File

@ -25,6 +25,7 @@ function test()
gDebuggee = aDebuggee; gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = false; gDebugger.DebuggerController.StackFrames.autoScopeExpand = false;
gDebugger.DebuggerView.Variables.delayedSearch = false;
prepareVariables(testVariablesFiltering); prepareVariables(testVariablesFiltering);
}); });
} }

View File

@ -25,6 +25,7 @@ function test()
gDebuggee = aDebuggee; gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true; gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.delayedSearch = false;
prepareVariables(testVariablesFiltering); prepareVariables(testVariablesFiltering);
}); });
} }

View File

@ -25,6 +25,7 @@ function test()
gDebuggee = aDebuggee; gDebuggee = aDebuggee;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true; gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.delayedSearch = false;
prepareVariables(testVariablesFiltering); prepareVariables(testVariablesFiltering);
}); });
} }

View File

@ -7,6 +7,7 @@
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties"; const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const LAZY_EMPTY_DELAY = 150; // ms const LAZY_EMPTY_DELAY = 150; // ms
const SEARCH_ACTION_MAX_DELAY = 1000; // ms
Components.utils.import('resource://gre/modules/Services.jsm'); Components.utils.import('resource://gre/modules/Services.jsm');
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
@ -210,6 +211,41 @@ VariablesView.prototype = {
*/ */
get searchEnabled() !!this._searchboxContainer, get searchEnabled() !!this._searchboxContainer,
/**
* Allows searches to be scheduled and delayed to avoid redundant calls.
*/
delayedSearch: true,
/**
* Schedules searching for variables or properties matching the query.
*
* @param string aQuery
* The variable or property to search for.
*/
scheduleSearch: function VV_scheduleSearch(aQuery) {
if (!this.delayedSearch) {
this.performSearch(aQuery);
return;
}
let delay = Math.max(SEARCH_ACTION_MAX_DELAY / aQuery.length, 0);
this.window.clearTimeout(this._searchTimeout);
this._searchFunction = this._startSearch.bind(this, aQuery);
this._searchTimeout = this.window.setTimeout(this._searchFunction, delay);
},
/**
* Immediately searches for variables or properties matching the query.
*
* @param string aQuery
* The variable or property to search for.
*/
performSearch: function VV_performSearch(aQuery) {
this.window.clearTimeout(this._searchTimeout);
this._searchFunction = null;
this._startSearch(aQuery);
},
/** /**
* Performs a case insensitive search for variables or properties matching * Performs a case insensitive search for variables or properties matching
* the query, and hides non-matched items. * the query, and hides non-matched items.
@ -217,7 +253,7 @@ VariablesView.prototype = {
* @param string aQuery * @param string aQuery
* The variable or property to search for. * The variable or property to search for.
*/ */
performSearch: function VV_performSerch(aQuery) { _startSearch: function VV__startSearch(aQuery) {
for (let [, scope] in this) { for (let [, scope] in this) {
switch (aQuery) { switch (aQuery) {
case "": case "":
@ -344,6 +380,8 @@ VariablesView.prototype = {
_prevHierarchy: null, _prevHierarchy: null,
_currHierarchy: null, _currHierarchy: null,
_emptyTimeout: null, _emptyTimeout: null,
_searchTimeout: null,
_searchFunction: null,
_enumVisible: true, _enumVisible: true,
_nonEnumVisible: true, _nonEnumVisible: true,
_parent: null, _parent: null,