Bug 762160 - Find Implementors of a function in the Debugger, r=past

This commit is contained in:
Victor Porof 2013-03-25 20:02:34 +02:00
parent 56f6beb13a
commit c3b98c48f6
23 changed files with 3913 additions and 321 deletions

View File

@ -13,7 +13,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");

View File

@ -38,8 +38,8 @@ Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
Cu.import("resource:///modules/devtools/VariablesView.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
XPCOMUtils.defineLazyModuleGetter(this,
"Reflect", "resource://gre/modules/reflect.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Parser",
"resource:///modules/devtools/Parser.jsm");
/**
* Object defining the debugger controller components.
@ -231,6 +231,11 @@ let DebuggerController = {
_onTabNavigated: function DC__onTabNavigated(aType, aPacket) {
if (aPacket.state == "start") {
DebuggerView._handleTabNavigation();
// Discard all the old sources.
DebuggerController.SourceScripts.clearCache();
DebuggerController.Parser.clearCache();
SourceUtils.clearCache();
return;
}
@ -955,9 +960,9 @@ StackFrames.prototype = {
// faulty expression, simply convert it to a string describing the error.
// There's no other information necessary to be offered in such cases.
let sanitizedExpressions = list.map(function(str) {
// Reflect.parse throws when encounters a syntax error.
// Reflect.parse throws when it encounters a syntax error.
try {
Reflect.parse(str);
Parser.reflectionAPI.parse(str);
return str; // Watch expression can be executed safely.
} catch (e) {
return "\"" + e.name + ": " + e.message + "\""; // Syntax error.
@ -1009,9 +1014,13 @@ StackFrames.prototype = {
* source script cache.
*/
function SourceScripts() {
this._cache = new Map(); // Can't use a WeakMap because keys are strings.
this._onNewSource = this._onNewSource.bind(this);
this._onNewGlobal = this._onNewGlobal.bind(this);
this._onSourcesAdded = this._onSourcesAdded.bind(this);
this._onFetch = this._onFetch.bind(this);
this._onTimeout = this._onTimeout.bind(this);
this._onFinished = this._onFinished.bind(this);
}
SourceScripts.prototype = {
@ -1154,35 +1163,161 @@ SourceScripts.prototype = {
* The source object coming from the active thread.
* @param function aCallback
* Function called after the source text has been loaded.
* @param function aOnTimeout
* @param function aTimeout
* Function called when the source text takes too long to fetch.
*/
getText: function SS_getText(aSource, aCallback, aOnTimeout) {
getText: function SS_getText(aSource, aCallback, aTimeout) {
// If already loaded, return the source text immediately.
if (aSource.loaded) {
aCallback(aSource.url, aSource.text);
aCallback(aSource);
return;
}
// If the source text takes too long to fetch, invoke a timeout to
// avoid blocking any operations.
if (aOnTimeout) {
var fetchTimeout = window.setTimeout(aOnTimeout, FETCH_SOURCE_RESPONSE_DELAY);
if (aTimeout) {
var fetchTimeout = window.setTimeout(() => {
aSource._fetchingTimedOut = true;
aTimeout(aSource);
}, FETCH_SOURCE_RESPONSE_DELAY);
}
// Get the source text from the active thread.
this.activeThread.source(aSource).source(function(aResponse) {
window.clearTimeout(fetchTimeout);
this.activeThread.source(aSource).source((aResponse) => {
if (aTimeout) {
window.clearTimeout(fetchTimeout);
}
if (aResponse.error) {
Cu.reportError("Error loading: " + aSource.url + "\n" + aResponse.message);
return void aCallback(aSource.url, "", aResponse.error);
return void aCallback(aSource);
}
aSource.loaded = true;
aSource.text = aResponse.source;
aCallback(aSource.url, aResponse.source);
aCallback(aSource);
});
}
},
/**
* Gets all the fetched sources.
*
* @return array
* An array containing [url, text] entries for the fetched sources.
*/
getCache: function SS_getCache() {
let sources = [];
for (let source of this._cache) {
sources.push(source);
}
return sources.sort(([first], [second]) => first > second);
},
/**
* Clears all the fetched sources from cache.
*/
clearCache: function SS_clearCache() {
this._cache = new Map();
},
/**
* Starts fetching all the sources, silently.
*
* @param array aUrls
* The urls for the sources to fetch.
* @param object aCallbacks [optional]
* An object containing the callback functions to invoke:
* - onFetch: optional, called after each source is fetched
* - onTimeout: optional, called when a source takes too long to fetch
* - onFinished: called when all the sources are fetched
*/
fetchSources: function SS_fetchSources(aUrls, aCallbacks = {}) {
this._fetchQueue = new Set();
this._fetchCallbacks = aCallbacks;
// Add each new source which needs to be fetched in a queue.
for (let url of aUrls) {
if (!this._cache.has(url)) {
this._fetchQueue.add(url);
}
}
// If all the sources were already fetched, don't do anything special.
if (this._fetchQueue.size == 0) {
this._onFinished();
return;
}
// Start fetching each new source.
for (let url of this._fetchQueue) {
let sourceItem = DebuggerView.Sources.getItemByValue(url);
let sourceObject = sourceItem.attachment.source;
this.getText(sourceObject, this._onFetch, this._onTimeout);
}
},
/**
* Called when a source has been fetched via fetchSources().
*
* @param object aSource
* The source object coming from the active thread.
*/
_onFetch: function SS__onFetch(aSource) {
// Remember the source in a cache so we don't have to fetch it again.
this._cache.set(aSource.url, aSource.text);
// Fetch completed before timeout, remove the source from the fetch queue.
this._fetchQueue.delete(aSource.url);
// If this fetch was eventually completed at some point after a timeout,
// don't call any subsequent event listeners.
if (aSource._fetchingTimedOut) {
return;
}
// Invoke the source fetch callback if provided via fetchSources();
if (this._fetchCallbacks.onFetch) {
this._fetchCallbacks.onFetch(aSource);
}
// Check if all sources were fetched and stored in the cache.
if (this._fetchQueue.size == 0) {
this._onFinished();
}
},
/**
* Called when a source's text takes too long to fetch via fetchSources().
*
* @param object aSource
* The source object coming from the active thread.
*/
_onTimeout: function SS__onTimeout(aSource) {
// Remove the source from the fetch queue.
this._fetchQueue.delete(aSource.url);
// Invoke the source timeout callback if provided via fetchSources();
if (this._fetchCallbacks.onTimeout) {
this._fetchCallbacks.onTimeout(aSource);
}
// Check if the remaining sources were fetched and stored in the cache.
if (this._fetchQueue.size == 0) {
this._onFinished();
}
},
/**
* Called when all the sources have been fetched.
*/
_onFinished: function SS__onFinished() {
// Invoke the finish callback if provided via fetchSources();
if (this._fetchCallbacks.onFinished) {
this._fetchCallbacks.onFinished();
}
},
_cache: null,
_fetchQueue: null,
_fetchCallbacks: null
};
/**
@ -1637,6 +1772,7 @@ XPCOMUtils.defineLazyGetter(window, "_isChromeDebugger", function() {
* Preliminary setup for the DebuggerController object.
*/
DebuggerController.initialize();
DebuggerController.Parser = new Parser();
DebuggerController.ThreadState = new ThreadState();
DebuggerController.StackFrames = new StackFrames();
DebuggerController.SourceScripts = new SourceScripts();

View File

@ -1513,11 +1513,8 @@ create({ constructor: WatchExpressionsView, proto: MenuContainer.prototype }, {
function GlobalSearchView() {
dumpn("GlobalSearchView was instantiated");
this._cache = new Map();
this._startSearch = this._startSearch.bind(this);
this._onFetchSourceFinished = this._onFetchSourceFinished.bind(this);
this._onFetchSourceTimeout = this._onFetchSourceTimeout.bind(this);
this._onFetchSourcesFinished = this._onFetchSourcesFinished.bind(this);
this._performGlobalSearch = this._performGlobalSearch.bind(this);
this._createItemView = this._createItemView.bind(this);
this._onScroll = this._onScroll.bind(this);
this._onHeaderClick = this._onHeaderClick.bind(this);
@ -1575,14 +1572,6 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
window.dispatchEvent(document, "Debugger:GlobalSearch:ViewCleared");
},
/**
* Clears all the fetched sources from cache.
*/
clearCache: function DVGS_clearCache() {
this._cache = new Map();
window.dispatchEvent(document, "Debugger:GlobalSearch:CacheCleared");
},
/**
* Focuses the next found match in the source editor.
*/
@ -1631,7 +1620,7 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
this.performSearch(aQuery);
return;
}
let delay = Math.max(GLOBAL_SEARCH_ACTION_MAX_DELAY / aQuery.length);
let delay = Math.max(GLOBAL_SEARCH_ACTION_MAX_DELAY / aQuery.length, 0);
window.clearTimeout(this._searchTimeout);
this._searchFunction = this._startSearch.bind(this, aQuery);
@ -1657,98 +1646,16 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
* The string to search for.
*/
_startSearch: function DVGS__startSearch(aQuery) {
let locations = DebuggerView.Sources.values;
this._sourcesCount = locations.length;
this._searchedToken = aQuery;
this._fetchSources(locations, {
onFetch: this._onFetchSourceFinished,
onTimeout: this._onFetchSourceTimeout,
onFinished: this._onFetchSourcesFinished
DebuggerController.SourceScripts.fetchSources(DebuggerView.Sources.values, {
onFinished: this._performGlobalSearch
});
},
/**
* Starts fetching all the sources, silently.
*
* @param array aLocations
* The locations for the sources to fetch.
* @param object aCallbacks
* An object containing the callback functions to invoke:
* - onFetch: called after each source is fetched
* - onTimeout: called when a source's text takes too long to fetch
* - onFinished: called if all the sources were already fetched
*/
_fetchSources:
function DVGS__fetchSources(aLocations, { onFetch, onTimeout, onFinished }) {
// If all the sources were already fetched, then don't do anything.
if (this._cache.size == aLocations.length) {
onFinished();
return;
}
// Fetch each new source.
for (let location of aLocations) {
if (this._cache.has(location)) {
continue;
}
let sourceItem = DebuggerView.Sources.getItemByValue(location);
let sourceObject = sourceItem.attachment.source;
DebuggerController.SourceScripts.getText(sourceObject, onFetch, onTimeout);
}
},
/**
* Called when a source has been fetched.
*
* @param string aLocation
* The location of the source.
* @param string aContents
* The text contents of the source.
*/
_onFetchSourceFinished: function DVGS__onFetchSourceFinished(aLocation, aContents, aError) {
if (aError) {
return;
}
// Remember the source in a cache so we don't have to fetch it again.
this._cache.set(aLocation, aContents);
// Check if all sources were fetched and stored in the cache.
if (this._cache.size == this._sourcesCount) {
this._onFetchSourcesFinished();
}
},
/**
* Called when a source's text takes too long to fetch.
*/
_onFetchSourceTimeout: function DVGS__onFetchSourceTimeout() {
// Remove the source from the load queue.
this._sourcesCount--;
// Check if the remaining sources were fetched and stored in the cache.
if (this._cache.size == this._sourcesCount) {
this._onFetchSourcesFinished();
}
},
/**
* Called when all the sources have been fetched.
*/
_onFetchSourcesFinished: function DVGS__onFetchSourcesFinished() {
// At least one source needs to be present to perform a global search.
if (!this._sourcesCount) {
return;
}
// All sources are fetched and stored in the cache, we can start searching.
this._performGlobalSearch();
this._sourcesCount = 0;
},
/**
* Finds string matches in all the sources stored in the cache, and groups
* them by location and line number.
* Finds string matches in all the sources stored in the controller's cache,
* and groups them by location and line number.
*/
_performGlobalSearch: function DVGS__performGlobalSearch() {
// Get the currently searched token from the filtering input.
@ -1767,8 +1674,9 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
// Prepare the results map, containing search details for each line.
let globalResults = new GlobalResults();
let sourcesCache = DebuggerController.SourceScripts.getCache();
for (let [location, contents] of this._cache) {
for (let [location, contents] of sourcesCache) {
// Verify that the search token is found anywhere in the source.
if (!contents.toLowerCase().contains(lowerCaseToken)) {
continue;
@ -2002,9 +1910,7 @@ create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
_forceExpandResults: false,
_searchTimeout: null,
_searchFunction: null,
_searchedToken: "",
_sourcesCount: -1,
_cache: null
_searchedToken: ""
});
/**

View File

@ -683,6 +683,8 @@ FilterView.prototype = {
this._searchboxHelpPanel = document.getElementById("searchbox-help-panel");
this._globalOperatorButton = document.getElementById("global-operator-button");
this._globalOperatorLabel = document.getElementById("global-operator-label");
this._functionOperatorButton = document.getElementById("function-operator-button");
this._functionOperatorLabel = document.getElementById("function-operator-label");
this._tokenOperatorButton = document.getElementById("token-operator-button");
this._tokenOperatorLabel = document.getElementById("token-operator-label");
this._lineOperatorButton = document.getElementById("line-operator-button");
@ -692,6 +694,7 @@ FilterView.prototype = {
this._fileSearchKey = LayoutHelpers.prettyKey(document.getElementById("fileSearchKey"), true);
this._globalSearchKey = LayoutHelpers.prettyKey(document.getElementById("globalSearchKey"), true);
this._filteredFunctionsKey = LayoutHelpers.prettyKey(document.getElementById("functionSearchKey"), true);
this._tokenSearchKey = LayoutHelpers.prettyKey(document.getElementById("tokenSearchKey"), true);
this._lineSearchKey = LayoutHelpers.prettyKey(document.getElementById("lineSearchKey"), true);
this._variableSearchKey = LayoutHelpers.prettyKey(document.getElementById("variableSearchKey"), true);
@ -703,12 +706,15 @@ FilterView.prototype = {
this._searchbox.addEventListener("blur", this._onBlur, false);
this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_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]));
this._functionOperatorLabel.setAttribute("value",
L10N.getFormatStr("searchPanelFunction", [this._filteredFunctionsKey]));
this._tokenOperatorLabel.setAttribute("value",
L10N.getFormatStr("searchPanelToken", [this._tokenSearchKey]));
this._lineOperatorLabel.setAttribute("value",
@ -755,22 +761,29 @@ FilterView.prototype = {
this._target = aView;
},
/**
* Gets the target container to be currently filtered.
* @return object
*/
get target() this._target,
/**
* Gets the entered file, line and token entered in the searchbox.
* @return array
*/
get searchboxInfo() {
let file, line, token, isGlobal, isVariable;
let operator, file, line, token;
let rawValue = this._searchbox.value;
let rawLength = rawValue.length;
let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG);
let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_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 or variable search, allow file or line flags.
if (globalFlagIndex != 0 && variableFlagIndex != 0) {
// This is not a global, function or variable search, allow file/line flags.
if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) {
let fileEnd = lineFlagIndex != -1
? lineFlagIndex
: tokenFlagIndex != -1 ? tokenFlagIndex : rawLength;
@ -779,49 +792,59 @@ FilterView.prototype = {
? tokenFlagIndex
: rawLength;
operator = "";
file = rawValue.slice(0, fileEnd);
line = ~~(rawValue.slice(fileEnd + 1, lineEnd)) || 0;
token = rawValue.slice(lineEnd + 1);
isGlobal = false;
isVariable = false;
}
// Global searches dissalow the use of file or line flags.
else if (globalFlagIndex == 0) {
operator = SEARCH_GLOBAL_FLAG;
file = "";
line = 0;
token = rawValue.slice(1);
}
// Function searches dissalow the use of file or line flags.
else if (functionFlagIndex == 0) {
operator = SEARCH_FUNCTION_FLAG;
file = "";
line = 0;
token = rawValue.slice(1);
isGlobal = true;
isVariable = false;
}
// Variable searches dissalow the use of file or line flags.
else if (variableFlagIndex == 0) {
operator = SEARCH_VARIABLE_FLAG;
file = "";
line = 0;
token = rawValue.slice(1);
isGlobal = false;
isVariable = true;
}
return [file, line, token, isGlobal, isVariable];
return [operator, file, line, token];
},
/**
* Returns the current searched operator.
* @return string
*/
get currentOperator() this.searchboxInfo[0],
/**
* Returns the currently searched file.
* @return string
*/
get searchedFile() this.searchboxInfo[0],
get searchedFile() this.searchboxInfo[1],
/**
* Returns the currently searched line.
* @return number
*/
get searchedLine() this.searchboxInfo[1],
get searchedLine() this.searchboxInfo[2],
/**
* Returns the currently searched token.
* @return string
*/
get searchedToken() this.searchboxInfo[2],
get searchedToken() this.searchboxInfo[3],
/**
* Clears the text from the searchbox and resets any changed view.
@ -955,25 +978,35 @@ FilterView.prototype = {
*/
_onSearch: function DVF__onScriptsSearch() {
this._searchboxHelpPanel.hidePopup();
let [file, line, token, isGlobal, isVariable] = this.searchboxInfo;
let [operator, file, line, token] = this.searchboxInfo;
// If this is a global search, schedule it for when the user stops typing,
// or hide the corresponding pane otherwise.
if (isGlobal) {
if (operator == SEARCH_GLOBAL_FLAG) {
DebuggerView.GlobalSearch.scheduleSearch(token);
this._prevSearchedToken = token;
return;
}
// If this is a function search, schedule it for when the user stops typing,
// or hide the corresponding panel otherwise.
if (operator == SEARCH_FUNCTION_FLAG) {
DebuggerView.FilteredFunctions.scheduleSearch(token);
this._prevSearchedToken = token;
return;
}
// If this is a variable search, defer the action to the corresponding
// variables view instance.
if (isVariable) {
if (operator == SEARCH_VARIABLE_FLAG) {
DebuggerView.Variables.scheduleSearch(token);
this._prevSearchedToken = token;
return;
}
DebuggerView.GlobalSearch.clearView();
DebuggerView.FilteredFunctions.clearView();
this._performFileSearch(file);
this._performLineSearch(line);
this._performTokenSearch(token);
@ -986,18 +1019,20 @@ FilterView.prototype = {
// This attribute is not implemented in Gecko at this time, see bug 680830.
e.char = String.fromCharCode(e.charCode);
let [file, line, token, isGlobal, isVariable] = this.searchboxInfo;
let isFileSearch, isLineSearch, isDifferentToken, isReturnKey;
let [operator, file, line, token] = this.searchboxInfo;
let isGlobal = operator == SEARCH_GLOBAL_FLAG;
let isFunction = operator == SEARCH_FUNCTION_FLAG;
let isVariable = operator == SEARCH_VARIABLE_FLAG;
let action = -1;
if (file && !line && !token) {
isFileSearch = true;
var isFileSearch = true;
}
if (line && !token) {
isLineSearch = true;
var isLineSearch = true;
}
if (this._prevSearchedToken != token) {
isDifferentToken = true;
var isDifferentToken = true;
}
// Meta+G and Ctrl+N focus next matches.
@ -1013,7 +1048,7 @@ FilterView.prototype = {
else switch (e.keyCode) {
case e.DOM_VK_RETURN:
case e.DOM_VK_ENTER:
isReturnKey = true;
var isReturnKey = true;
// fall through
case e.DOM_VK_DOWN:
action = 0;
@ -1030,8 +1065,7 @@ FilterView.prototype = {
DebuggerView.editor.focus();
return;
}
if (action == -1 || (!file && !line && !token)) {
DebuggerView.FilteredSources.hidden = true;
if (action == -1 || (!operator && !file && !line && !token)) {
return;
}
@ -1041,7 +1075,7 @@ FilterView.prototype = {
// Select the next or previous file search entry.
if (isFileSearch) {
if (isReturnKey) {
DebuggerView.FilteredSources.hidden = true;
DebuggerView.FilteredSources.clearView();
DebuggerView.editor.focus();
this.clearSearch();
} else {
@ -1062,6 +1096,21 @@ FilterView.prototype = {
return;
}
// Perform a function search based on the specified operator.
if (isFunction) {
if (isReturnKey && (isDifferentToken || DebuggerView.FilteredFunctions.hidden)) {
DebuggerView.FilteredFunctions.performSearch(token);
} else if (!isReturnKey) {
DebuggerView.FilteredFunctions[["focusNext", "focusPrev"][action]]();
} else {
DebuggerView.FilteredFunctions.clearView();
DebuggerView.editor.focus();
this.clearSearch();
}
this._prevSearchedToken = token;
return;
}
// Perform a variable search based on the specified operator.
if (isVariable) {
if (isReturnKey && isDifferentToken) {
@ -1097,6 +1146,8 @@ FilterView.prototype = {
*/
_onBlur: function DVF__onBlur() {
DebuggerView.GlobalSearch.clearView();
DebuggerView.FilteredSources.clearView();
DebuggerView.FilteredFunctions.clearView();
DebuggerView.Variables.performSearch(null);
this._searchboxHelpPanel.hidePopup();
},
@ -1109,6 +1160,7 @@ FilterView.prototype = {
*/
_doSearch: function DVF__doSearch(aOperator = "") {
this._searchbox.focus();
this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
this._searchbox.value = aOperator;
},
@ -1128,6 +1180,14 @@ FilterView.prototype = {
this._searchboxHelpPanel.hidePopup();
},
/**
* Called when the source function filter key sequence was pressed.
*/
_doFunctionSearch: function DVF__doFunctionSearch() {
this._doSearch(SEARCH_FUNCTION_FLAG);
this._searchboxHelpPanel.hidePopup();
},
/**
* Called when the source token filter key sequence was pressed.
*/
@ -1165,6 +1225,8 @@ FilterView.prototype = {
_searchboxHelpPanel: null,
_globalOperatorButton: null,
_globalOperatorLabel: null,
_functionOperatorButton: null,
_functionOperatorLabel: null,
_tokenOperatorButton: null,
_tokenOperatorLabel: null,
_lineOperatorButton: null,
@ -1173,6 +1235,7 @@ FilterView.prototype = {
_variableOperatorLabel: null,
_fileSearchKey: "",
_globalSearchKey: "",
_filteredFunctionsKey: "",
_tokenSearchKey: "",
_lineSearchKey: "",
_variableSearchKey: "",
@ -1187,23 +1250,20 @@ FilterView.prototype = {
*/
function FilteredSourcesView() {
dumpn("FilteredSourcesView was instantiated");
ResultsPanelContainer.call(this);
this._onClick = this._onClick.bind(this);
this.onClick = this.onClick.bind(this);
this.onSelect = this.onSelect.bind(this);
}
create({ constructor: FilteredSourcesView, proto: MenuContainer.prototype }, {
create({ constructor: FilteredSourcesView, proto: ResultsPanelContainer.prototype }, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVFS_initialize() {
dumpn("Initializing the FilteredSourcesView");
this.node = new ListWidget(document.getElementById("filtered-sources-panel"));
this._searchbox = document.getElementById("searchbox");
this.node.itemFactory = this._createItemView;
this.node.itemType = "vbox";
this.node.addEventListener("click", this._onClick, false);
this.anchor = document.getElementById("searchbox");
},
/**
@ -1212,19 +1272,7 @@ create({ constructor: FilteredSourcesView, proto: MenuContainer.prototype }, {
destroy: function DVFS_destroy() {
dumpn("Destroying the FilteredSourcesView");
this.node.removeEventListener("click", this._onClick, false);
},
/**
* Sets the files container hidden or visible. It's hidden by default.
* @param boolean aFlag
*/
set hidden(aFlag) {
if (aFlag) {
this.node._parent.hidePopup();
} else {
this.node._parent.openPopup(this._searchbox);
}
this.anchor = null;
},
/**
@ -1234,7 +1282,7 @@ create({ constructor: FilteredSourcesView, proto: MenuContainer.prototype }, {
this.empty();
// If there's no currently searched file, or there are no matches found,
// hide the popup.
// hide the popup and avoid creating the view again.
if (!DebuggerView.Filtering.searchedFile ||
!DebuggerView.Sources.visibleItems.length) {
this.hidden = true;
@ -1243,12 +1291,12 @@ create({ constructor: FilteredSourcesView, proto: MenuContainer.prototype }, {
// Get the currently visible items in the sources container.
let visibleItems = DebuggerView.Sources.visibleItems;
let displayedItems = visibleItems.slice(0, FILTERED_SOURCES_MAX_RESULTS);
let displayedItems = visibleItems.slice(0, RESULTS_PANEL_MAX_RESULTS);
for (let item of displayedItems) {
// Append a location item item to this container.
let trimmedLabel = SourceUtils.trimUrlLength(item.label);
let trimmedValue = SourceUtils.trimUrlLength(item.value);
let trimmedValue = SourceUtils.trimUrlLength(item.value, 0, "start");
let locationItem = this.push([trimmedLabel, trimmedValue], {
relaxed: true, /* this container should allow dupes & degenerates */
attachment: {
@ -1258,82 +1306,263 @@ create({ constructor: FilteredSourcesView, proto: MenuContainer.prototype }, {
});
}
this._updateSelection(this.getItemAtIndex(0));
this.hidden = false;
},
// Select the first entry in this container.
this.select(0);
/**
* Focuses the next found match in this container.
*/
focusNext: function DVFS_focusNext() {
let nextIndex = this.selectedIndex + 1;
if (nextIndex >= this.itemCount) {
nextIndex = 0;
}
this._updateSelection(this.getItemAtIndex(nextIndex));
},
/**
* Focuses the previously found match in this container.
*/
focusPrev: function DVFS_focusPrev() {
let prevIndex = this.selectedIndex - 1;
if (prevIndex < 0) {
prevIndex = this.itemCount - 1;
}
this._updateSelection(this.getItemAtIndex(prevIndex));
// Only display the results panel if there's at least one entry available.
this.hidden = this.itemCount == 0;
},
/**
* The click listener for this container.
*/
_onClick: function DVFS__onClick(e) {
onClick: function DVFS_onClick(e) {
let locationItem = this.getItemForElement(e.target);
if (locationItem) {
this._updateSelection(locationItem);
this.select(locationItem);
DebuggerView.Filtering.clearSearch();
}
},
/**
* Updates the selected item in this container and other views.
* The select listener for this container.
*
* @param MenuItem aItem
* The item associated with the element to select.
*/
_updateSelection: function DVFS__updateSelection(aItem) {
this.selectedItem = aItem;
DebuggerView.Filtering._target.selectedValue = aItem.attachment.fullValue;
onSelect: function DVFS_onSelect(e) {
let locationItem = this.getItemForElement(e.target);
if (locationItem) {
DebuggerView.Sources.selectedValue = locationItem.attachment.fullValue;
}
}
});
/**
* Functions handling the function search UI.
*/
function FilteredFunctionsView() {
dumpn("FilteredFunctionsView was instantiated");
ResultsPanelContainer.call(this);
this._performFunctionSearch = this._performFunctionSearch.bind(this);
this.onClick = this.onClick.bind(this);
this.onSelect = this.onSelect.bind(this);
}
create({ constructor: FilteredFunctionsView, proto: ResultsPanelContainer.prototype }, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function DVFF_initialize() {
dumpn("Initializing the FilteredFunctionsView");
this.anchor = document.getElementById("searchbox");
},
/**
* Customization function for creating an item's UI.
*
* @param nsIDOMNode aElementNode
* The element associated with the displayed item.
* @param any aAttachment
* Some attached primitive/object.
* @param string aLabel
* The item's label.
* @param string aValue
* The item's value.
* Destruction function, called when the debugger is closed.
*/
_createItemView:
function DVFS__createItemView(aElementNode, aAttachment, aLabel, aValue) {
let labelNode = document.createElement("label");
labelNode.className = "plain dbg-source-item-name";
labelNode.setAttribute("value", aLabel);
destroy: function DVFF_destroy() {
dumpn("Destroying the FilteredFunctionsView");
let valueNode = document.createElement("label");
valueNode.setAttribute("value", aValue);
valueNode.className = "plain dbg-source-item-details";
aElementNode.className = "light dbg-source-item";
aElementNode.appendChild(labelNode);
aElementNode.appendChild(valueNode);
this.anchor = null;
},
_searchbox: null
/**
* Allows searches to be scheduled and delayed to avoid redundant calls.
*/
delayedSearch: true,
/**
* Schedules searching for a function in all of the sources.
*
* @param string aQuery
* The function to search for.
*/
scheduleSearch: function DVFF_scheduleSearch(aQuery) {
if (!this.delayedSearch) {
this.performSearch(aQuery);
return;
}
let delay = Math.max(FUNCTION_SEARCH_ACTION_MAX_DELAY / aQuery.length, 0);
window.clearTimeout(this._searchTimeout);
this._searchFunction = this._startSearch.bind(this, aQuery);
this._searchTimeout = window.setTimeout(this._searchFunction, delay);
},
/**
* Immediately searches for a function in all of the sources.
*
* @param string aQuery
* The function to search for.
*/
performSearch: function DVFF_performSearch(aQuery) {
window.clearTimeout(this._searchTimeout);
this._searchFunction = null;
this._startSearch(aQuery);
},
/**
* Starts searching for a function in all of the sources.
*
* @param string aQuery
* The function to search for.
*/
_startSearch: function DVFF__startSearch(aQuery) {
this._searchedToken = aQuery;
DebuggerController.SourceScripts.fetchSources(DebuggerView.Sources.values, {
onFinished: this._performFunctionSearch
});
},
/**
* Finds function matches in all the sources stored in the cache, and groups
* them by location and line number.
*/
_performFunctionSearch: function DVFF__performFunctionSearch() {
// Get the currently searched token from the filtering input.
// Continue parsing even if the searched token is an empty string, to
// cache the syntax tree nodes generated by the reflection API.
let token = this._searchedToken;
// Make sure the currently displayed source is parsed first. Once the
// maximum allowed number of resutls are found, parsing will be halted.
let sourcesCache = DebuggerController.SourceScripts.getCache();
let currentUrl = DebuggerView.Sources.selectedValue;
sourcesCache.sort(function([sourceUrl]) sourceUrl == currentUrl ? -1 : 1);
// If not searching for a specific function, only parse the displayed source.
if (!token) {
sourcesCache.splice(1);
}
// Prepare the results array, containing search details for each source.
let searchResults = [];
for (let [location, contents] of sourcesCache) {
let parserMethods = DebuggerController.Parser.get(location, contents);
let sourceResults = parserMethods.getNamedFunctionDefinitions(token);
for (let scriptResult of sourceResults) {
for (let parseResult of scriptResult.parseResults) {
searchResults.push({
sourceUrl: scriptResult.sourceUrl,
scriptOffset: scriptResult.scriptOffset,
functionName: parseResult.functionName,
functionLocation: parseResult.functionLocation,
inferredName: parseResult.inferredName,
inferredChain: parseResult.inferredChain,
inferredLocation: parseResult.inferredLocation
});
// Once the maximum allowed number of results is reached, proceed
// with building the UI immediately.
if (searchResults.length >= RESULTS_PANEL_MAX_RESULTS) {
this._syncFunctionSearch(searchResults);
return;
}
}
}
}
// Couldn't reach the maximum allowed number of results, but that's ok,
// continue building the UI.
this._syncFunctionSearch(searchResults);
},
/**
* Updates the list of functions displayed in this container.
*
* @param array aSearchResults
* The results array, containing search details for each source.
*/
_syncFunctionSearch: function DVFF__syncFunctionSearch(aSearchResults) {
this.empty();
// Show the popup even if the search token is an empty string. If there are
// no matches found, hide the popup and avoid creating the view again.
if (!aSearchResults.length) {
this.hidden = true;
return;
}
for (let item of aSearchResults) {
// Some function expressions don't necessarily have a name, but the
// parser provides us with an inferred name from an enclosing
// VariableDeclarator, AssignmentExpression, ObjectExpression node.
if (item.functionName && item.inferredName &&
item.functionName != item.inferredName) {
let s = " " + L10N.getStr("functionSearchSeparatorLabel") + " ";
item.displayedName = item.inferredName + s + item.functionName;
}
// The function doesn't have an explicit name, but it could be inferred.
else if (item.inferredName) {
item.displayedName = item.inferredName;
}
// The function only has an explicit name.
else {
item.displayedName = item.functionName;
}
// Some function expressions have unexpected bounds, since they may not
// necessarily have an associated name defining them.
if (item.inferredLocation) {
item.actualLocation = item.inferredLocation;
} else {
item.actualLocation = item.functionLocation;
}
// Append a function item to this container.
let trimmedLabel = SourceUtils.trimUrlLength(item.displayedName + "()");
let trimmedValue = SourceUtils.trimUrlLength(item.sourceUrl, 0, "start");
let description = (item.inferredChain || []).join(".");
let functionItem = this.push([trimmedLabel, trimmedValue, description], {
index: -1, /* specifies on which position should the item be appended */
relaxed: true, /* this container should allow dupes & degenerates */
attachment: item
});
}
// Select the first entry in this container.
this.select(0);
this.hidden = this.itemCount == 0;
},
/**
* The click listener for this container.
*/
onClick: function DVFF_onClick(e) {
let functionItem = this.getItemForElement(e.target);
if (functionItem) {
this.select(functionItem);
DebuggerView.Filtering.clearSearch();
}
},
/**
* The select listener for this container.
*/
onSelect: function DVFF_onSelect(e) {
let functionItem = this.getItemForElement(e.target);
if (functionItem) {
let sourceUrl = functionItem.attachment.sourceUrl;
let scriptOffset = functionItem.attachment.scriptOffset;
let actualLocation = functionItem.attachment.actualLocation;
DebuggerView.updateEditor(sourceUrl, actualLocation.start.line, {
charOffset: scriptOffset,
columnOffset: actualLocation.start.column,
noDebug: true
});
}
},
_searchTimeout: null,
_searchFunction: null,
_searchedToken: ""
});
/**
@ -1343,5 +1572,6 @@ DebuggerView.Toolbar = new ToolbarView();
DebuggerView.Options = new OptionsView();
DebuggerView.Filtering = new FilterView();
DebuggerView.FilteredSources = new FilteredSourcesView();
DebuggerView.FilteredFunctions = new FilteredFunctionsView();
DebuggerView.ChromeGlobals = new ChromeGlobalsView();
DebuggerView.StackFrames = new StackFramesView();

View File

@ -17,11 +17,14 @@ const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
const FILTERED_SOURCES_MAX_RESULTS = 10;
const RESULTS_PANEL_POPUP_POSITION = "before_end";
const RESULTS_PANEL_MAX_RESULTS = 10;
const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
const SEARCH_GLOBAL_FLAG = "!";
const SEARCH_FUNCTION_FLAG = "@";
const SEARCH_TOKEN_FLAG = "#";
const SEARCH_LINE_FLAG = ":";
const SEARCH_VARIABLE_FLAG = "*";
@ -46,6 +49,7 @@ let DebuggerView = {
this.Options.initialize();
this.Filtering.initialize();
this.FilteredSources.initialize();
this.FilteredFunctions.initialize();
this.ChromeGlobals.initialize();
this.StackFrames.initialize();
this.Sources.initialize();
@ -76,6 +80,7 @@ let DebuggerView = {
this.Options.destroy();
this.Filtering.destroy();
this.FilteredSources.destroy();
this.FilteredFunctions.destroy();
this.ChromeGlobals.destroy();
this.StackFrames.destroy();
this.Sources.destroy();
@ -260,7 +265,7 @@ let DebuggerView = {
// If the source is not loaded, display a placeholder text.
if (!aSource.loaded) {
DebuggerController.SourceScripts.getText(aSource, set.bind(this, aSource));
DebuggerController.SourceScripts.getText(aSource, set.bind(this));
}
// If the source is already loaded, display it immediately.
else {
@ -371,10 +376,10 @@ let DebuggerView = {
aLine += aFlags.lineOffset;
}
if (!aFlags.noCaret) {
editor.setCaretPosition(aLine - 1);
editor.setCaretPosition(aLine - 1, aFlags.columnOffset);
}
if (!aFlags.noDebug) {
editor.setDebugLocation(aLine - 1);
editor.setDebugLocation(aLine - 1, aFlags.columnOffset);
}
}
},
@ -388,13 +393,24 @@ let DebuggerView = {
* @return string
* The specified line's text.
*/
getEditorLine: function SS_getEditorLine(aLine) {
getEditorLine: function DV_getEditorLine(aLine) {
let line = aLine || this.editor.getCaretPosition().line;
let start = this.editor.getLineStart(line);
let end = this.editor.getLineEnd(line);
return this.editor.getText(start, end);
},
/**
* Gets the text in the source editor's selection bounds.
*
* @return string
* The selected text.
*/
getEditorSelection: function DV_getEditorSelection() {
let selection = this.editor.getSelection();
return this.editor.getText(selection.start, selection.end);
},
/**
* Gets the visibility state of the instruments pane.
* @return boolean
@ -486,13 +502,13 @@ let DebuggerView = {
dumpn("Handling tab navigation in the DebuggerView");
this.Filtering.clearSearch();
this.FilteredSources.clearView();
this.FilteredFunctions.clearView();
this.GlobalSearch.clearView();
this.GlobalSearch.clearCache();
this.ChromeGlobals.empty();
this.StackFrames.empty();
this.Sources.empty();
this.Variables.empty();
SourceUtils.clearCache();
if (this.editor) {
this.editor.setText("");
@ -750,6 +766,202 @@ ListWidget.prototype = {
_emptyTextValue: ""
};
/**
* A custom items container, used for displaying views like the
* FilteredSources, FilteredFunctions etc., inheriting the generic MenuContainer.
*/
function ResultsPanelContainer() {
this._createItemView = this._createItemView.bind(this);
}
create({ constructor: ResultsPanelContainer, proto: MenuContainer.prototype }, {
onClick: null,
onSelect: null,
/**
* Sets the anchor node for this container panel.
* @param nsIDOMNode aNode
*/
set anchor(aNode) {
this._anchor = aNode;
// If the anchor node is not null, create a panel to attach to the anchor
// when showing the popup.
if (aNode) {
if (!this._panel) {
this._panel = document.createElement("panel");
this._panel.className = "results-panel";
this._panel.setAttribute("level", "top");
this._panel.setAttribute("noautofocus", "true");
document.documentElement.appendChild(this._panel);
}
if (!this.node) {
this.node = new ListWidget(this._panel);
this.node.itemType = "vbox";
this.node.itemFactory = this._createItemView;
this.node.addEventListener("click", this.onClick, false);
}
}
// Cleanup the anchor and remove the previously created panel.
else {
if (this._panel) {
document.documentElement.removeChild(this._panel);
this._panel = null;
}
if (this.node) {
this.node.removeEventListener("click", this.onClick, false);
this.node = null;
}
}
},
/**
* Gets the anchor node for this container panel.
* @return nsIDOMNode
*/
get anchor() this._anchor,
/**
* Sets the default top, left and position params when opening the panel.
* @param object aOptions
*/
set options(aOptions) {
this._top = aOptions.top;
this._left = aOptions.left;
this._position = aOptions.position;
},
/**
* Gets the default params for when opening the panel.
* @return object
*/
get options() ({
top: this._top,
left: this._left,
position: this._position
}),
/**
* Sets the container panel hidden or visible. It's hidden by default.
* @param boolean aFlag
*/
set hidden(aFlag) {
if (aFlag) {
this._panel.hidePopup();
} else {
this._panel.openPopup(this._anchor, this._position, this._left, this._top);
this.anchor.focus();
}
},
/**
* Gets this container's visibility state.
* @return boolean
*/
get hidden()
this._panel.state == "closed" ||
this._panel.state == "hiding",
/**
* Removes all items from this container and hides it.
*/
clearView: function RPC_clearView() {
this.hidden = true;
this.empty();
window.dispatchEvent(document, "Debugger:ResultsPanelContainer:ViewCleared");
},
/**
* Focuses the next found item in this container.
*/
focusNext: function RPC_focusNext() {
let nextIndex = this.selectedIndex + 1;
if (nextIndex >= this.itemCount) {
nextIndex = 0;
}
this.select(this.getItemAtIndex(nextIndex));
},
/**
* Focuses the previously found item in this container.
*/
focusPrev: function RPC_focusPrev() {
let prevIndex = this.selectedIndex - 1;
if (prevIndex < 0) {
prevIndex = this.itemCount - 1;
}
this.select(this.getItemAtIndex(prevIndex));
},
/**
* Updates the selected item in this container.
*
* @param MenuItem | number aItem
* The item associated with the element to select.
*/
select: function RPC_select(aItem) {
if (typeof aItem == "number") {
this.select(this.getItemAtIndex(aItem));
return;
}
// Update the currently selected item in this container using the
// selectedItem setter in the MenuContainer prototype chain.
this.selectedItem = aItem;
// Invoke the attached selection callback if available in any
// inheriting prototype.
if (this.onSelect) {
this.onSelect({ target: aItem.target });
}
},
/**
* Customization function for creating an item's UI.
*
* @param nsIDOMNode aElementNode
* The element associated with the displayed item.
* @param any aAttachment
* Some attached primitive/object.
* @param string aLabel
* The item's label.
* @param string aValue
* The item's value.
* @param string aDescription
* An optional description of the item.
*/
_createItemView:
function RPC__createItemView(aElementNode, aAttachment, aLabel, aValue, aDescription) {
let labelsGroup = document.createElement("hbox");
if (aDescription) {
let preLabelNode = document.createElement("label");
preLabelNode.className = "plain results-panel-item-pre";
preLabelNode.setAttribute("value", aDescription);
labelsGroup.appendChild(preLabelNode);
}
if (aLabel) {
let labelNode = document.createElement("label");
labelNode.className = "plain results-panel-item-name";
labelNode.setAttribute("value", aLabel);
labelsGroup.appendChild(labelNode);
}
let valueNode = document.createElement("label");
valueNode.className = "plain results-panel-item-details";
valueNode.setAttribute("value", aValue);
aElementNode.className = "light results-panel-item";
aElementNode.appendChild(labelsGroup);
aElementNode.appendChild(valueNode);
},
_anchor: null,
_panel: null,
_position: RESULTS_PANEL_POPUP_POSITION,
_left: 0,
_top: 0
});
/**
* A simple way of displaying a "Connect to..." prompt.
*/

View File

@ -38,6 +38,8 @@
oncommand="DebuggerView.Filtering._doFileSearch()"/>
<command id="globalSearchCommand"
oncommand="DebuggerView.Filtering._doGlobalSearch()"/>
<command id="functionSearchCommand"
oncommand="DebuggerView.Filtering._doFunctionSearch()"/>
<command id="tokenSearchCommand"
oncommand="DebuggerView.Filtering._doTokenSearch()"/>
<command id="lineSearchCommand"
@ -94,6 +96,11 @@
accesskey="&debuggerUI.searchGlobal.key;"
key="globalSearchKey"
command="globalSearchCommand"/>
<menuitem id="se-dbg-cMenu-findFunction"
label="&debuggerUI.searchFunction;"
accesskey="&debuggerUI.searchFunction.key;"
key="functionSearchKey"
command="functionSearchCommand"/>
<menuseparator/>
<menuitem id="se-dbg-cMenu-findToken"
label="&debuggerUI.searchToken;"
@ -174,10 +181,22 @@
key="&debuggerUI.searchFile.key;"
modifiers="accel"
command="fileSearchCommand"/>
<key id="fileSearchKey"
key="&debuggerUI.searchFile.altkey;"
modifiers="accel"
command="fileSearchCommand"/>
<key id="globalSearchKey"
key="&debuggerUI.searchGlobal.key;"
modifiers="accel alt"
command="globalSearchCommand"/>
<key id="functionSearchKey"
key="&debuggerUI.searchFunction.key;"
modifiers="accel"
command="functionSearchCommand"/>
<key id="functionSearchKey"
key="&debuggerUI.searchFunction.altkey;"
modifiers="accel shift"
command="functionSearchCommand"/>
<key id="tokenSearchKey"
key="&debuggerUI.searchToken.key;"
modifiers="accel"
@ -278,6 +297,13 @@
<label id="global-operator-label"
class="plain searchbox-panel-operator-label"/>
</hbox>
<hbox align="center">
<button id="function-operator-button"
class="searchbox-panel-operator-button"
command="functionSearchCommand"/>
<label id="function-operator-label"
class="plain searchbox-panel-operator-label"/>
</hbox>
<hbox align="center">
<button id="token-operator-button"
class="searchbox-panel-operator-button"
@ -295,19 +321,13 @@
<hbox align="center">
<button id="variable-operator-button"
class="searchbox-panel-operator-button"
command="variableSearchCommand"/>
command="variableSearchCommand"/>
<label id="variable-operator-label"
class="plain searchbox-panel-operator-label"/>
</hbox>
</vbox>
</panel>
<panel id="filtered-sources-panel"
level="top"
noautofocus="true"
position="before_start">
</panel>
<panel id="conditional-breakpoint-panel"
hidden="true"
level="top"

View File

@ -11,10 +11,10 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_TESTS = \
$(browser_dbg_cmd_break.js disabled until bug 722727 is fixed) \
browser_dbg_cmd.js \
browser_dbg_aaa_run_first_leaktest.js \
browser_dbg_clean-exit.js \
browser_dbg_cmd.js \
$(browser_dbg_cmd_break.js disabled until bug 722727 is fixed) \
browser_dbg_createChrome.js \
$(browser_dbg_createRemote.js disabled for intermittent failures, bug 753225) \
$(browser_dbg_debugger-tab-switch.js disabled until issues 106, 40 are fixed) \
@ -63,6 +63,7 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_location-changes.js \
browser_dbg_location-changes-new.js \
browser_dbg_location-changes-blank.js \
browser_dbg_sources-cache.js \
browser_dbg_scripts-switching.js \
browser_dbg_scripts-sorting.js \
browser_dbg_scripts-searching-01.js \
@ -75,6 +76,7 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_scripts-searching-08.js \
browser_dbg_scripts-searching-files_ui.js \
browser_dbg_scripts-searching-popup.js \
browser_dbg_function-search.js \
browser_dbg_pause-resume.js \
browser_dbg_pause-warning.js \
browser_dbg_update-editor-mode.js \
@ -122,6 +124,11 @@ MOCHITEST_BROWSER_PAGES = \
browser_dbg_breakpoint-new-script.html \
browser_dbg_conditional-breakpoints.html \
browser_dbg_watch-expressions.html \
browser_dbg_function-search-01.html \
browser_dbg_function-search-02.html \
test-function-search-01.js \
test-function-search-02.js \
test-function-search-03.js \
$(NULL)
MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES

View File

@ -1,5 +1,9 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="application/javascript;version=1.7"/>
let output;

View File

@ -0,0 +1,17 @@
<!DOCTYPE HTML>
<html>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<meta charset='utf-8'/>
<title>Browser Debugger Function Search</title>
<script type="text/javascript" src="test-function-search-01.js"></script>
<script type="text/javascript" src="test-function-search-02.js"></script>
<script type="text/javascript" src="test-function-search-03.js"></script>
</head>
<body>
<p>Peanut butter jelly time!</p>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<head>
<meta charset='utf-8'/>
<title>Browser Debugger Function Search</title>
<script type="text/javascript" src="test-function-search-01.js"></script>
<script type="text/javascript" src="test-function-search-02.js"></script>
<script type="text/javascript" src="test-function-search-03.js"></script>
</head>
<body>
<p>Peanut butter jelly time!</p>
<script type="text/javascript">
function inline() {
}
</script>
</body>
</html>

View File

@ -0,0 +1,495 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TAB_URL = EXAMPLE_URL + "browser_dbg_function-search-02.html";
/**
* Tests if the function searching works properly.
*/
let gPane = null;
let gTab = null;
let gDebuggee = null;
let gDebugger = null;
let gEditor = null;
let gSources = null;
let gSearchBox = null;
let gFilteredFunctions = null;
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
Services.tm.currentThread.dispatch({ run: testFunctionsFilter }, 0);
});
});
}
function testFunctionsFilter()
{
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
gFilteredFunctions = gDebugger.DebuggerView.FilteredFunctions;
htmlSearch(function() {
showSource("test-function-search-01.js", function() {
firstSearch(function() {
showSource("test-function-search-02.js", function() {
secondSearch(function() {
showSource("test-function-search-03.js", function() {
thirdSearch(function() {
saveSearch(function() {
filterSearch(function() {
bogusSearch(function() {
anotherSearch(function() {
emptySearch(function() {
closeDebuggerAndFinish();
});
})
})
});
});
});
});
});
});
});
});
});
}
function htmlSearch(callback) {
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
info("Current script url:\n" + gSources.selectedValue + "\n");
info("Debugger editor text:\n" + gEditor.getText() + "\n");
ok(gFilteredFunctions.selectedValue,
"An item should be selected in the filtered functions view");
ok(gFilteredFunctions.selectedLabel,
"An item should be selected in the filtered functions view");
let url = gSources.selectedValue;
if (url.indexOf("-02.html") != -1) {
executeSoon(function() {
let expectedResults = [
["inline", "-02.html", "", 16, 15],
]
for (let [label, value, description, line, col] of expectedResults) {
is(gFilteredFunctions.selectedItem.label,
gDebugger.SourceUtils.trimUrlLength(label + "()"),
"The corect label (" + label + ") is currently selected.");
ok(gFilteredFunctions.selectedItem.value.contains(value),
"The corect value (" + value + ") is attached.");
is(gFilteredFunctions.selectedItem.description, description,
"The corect description (" + description + ") is currently shown.");
info("Editor caret position: " + gEditor.getCaretPosition().toSource());
ok(gEditor.getCaretPosition().line == line &&
gEditor.getCaretPosition().col == col,
"The editor didn't jump to the correct line.");
ok(gSources.selectedLabel, label,
"The current source isn't the correct one, according to the label.");
ok(gSources.selectedValue, value,
"The current source isn't the correct one, according to the value.");
EventUtils.sendKey("DOWN", gDebugger);
}
ok(gEditor.getCaretPosition().line == expectedResults[0][3] &&
gEditor.getCaretPosition().col == expectedResults[0][4],
"The editor didn't jump to the correct line again.");
executeSoon(callback);
});
} else {
ok(false, "How did you get here? Go away, you.");
}
});
write("@inline");
}
function firstSearch(callback) {
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
info("Current script url:\n" + gSources.selectedValue + "\n");
info("Debugger editor text:\n" + gEditor.getText() + "\n");
ok(gFilteredFunctions.selectedValue,
"An item should be selected in the filtered functions view");
ok(gFilteredFunctions.selectedLabel,
"An item should be selected in the filtered functions view");
let url = gSources.selectedValue;
if (url.indexOf("-01.js") != -1) {
executeSoon(function() {
let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
let expectedResults = [
["test", "-01.js", "", 3, 9],
["anonymousExpression", "-01.js", "test.prototype", 8, 2],
["namedExpression" + s + "NAME", "-01.js", "test.prototype", 10, 2],
["a_test", "-01.js", "foo", 21, 2],
["n_test" + s + "x", "-01.js", "foo", 23, 2],
["a_test", "-01.js", "foo.sub", 26, 4],
["n_test" + s + "y", "-01.js", "foo.sub", 28, 4],
["a_test", "-01.js", "foo.sub.sub", 31, 6],
["n_test" + s + "z", "-01.js", "foo.sub.sub", 33, 6],
["test_SAME_NAME", "-01.js", "foo.sub.sub.sub", 36, 8]
]
for (let [label, value, description, line, col] of expectedResults) {
is(gFilteredFunctions.selectedItem.label,
gDebugger.SourceUtils.trimUrlLength(label + "()"),
"The corect label (" + label + ") is currently selected.");
ok(gFilteredFunctions.selectedItem.value.contains(value),
"The corect value (" + value + ") is attached.");
is(gFilteredFunctions.selectedItem.description, description,
"The corect description (" + description + ") is currently shown.");
info("Editor caret position: " + gEditor.getCaretPosition().toSource());
ok(gEditor.getCaretPosition().line == line &&
gEditor.getCaretPosition().col == col,
"The editor didn't jump to the correct line.");
ok(gSources.selectedLabel, label,
"The current source isn't the correct one, according to the label.");
ok(gSources.selectedValue, value,
"The current source isn't the correct one, according to the value.");
EventUtils.sendKey("DOWN", gDebugger);
}
ok(gEditor.getCaretPosition().line == expectedResults[0][3] &&
gEditor.getCaretPosition().col == expectedResults[0][4],
"The editor didn't jump to the correct line again.");
executeSoon(callback);
});
} else {
ok(false, "How did you get here? Go away, you.");
}
});
write("@");
}
function secondSearch(callback) {
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
info("Current script url:\n" + gSources.selectedValue + "\n");
info("Debugger editor text:\n" + gEditor.getText() + "\n");
ok(gFilteredFunctions.selectedValue,
"An item should be selected in the filtered functions view");
ok(gFilteredFunctions.selectedLabel,
"An item should be selected in the filtered functions view");
let url = gSources.selectedValue;
if (url.indexOf("-02.js") != -1) {
executeSoon(function() {
let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
let expectedResults = [
["test2", "-02.js", "", 3, 4],
["test3" + s + "test3_NAME", "-02.js", "", 7, 4],
["test4_SAME_NAME", "-02.js", "", 10, 4],
["x" + s + "X", "-02.js", "test.prototype", 13, 0],
["y" + s + "Y", "-02.js", "test.prototype.sub", 15, 0],
["z" + s + "Z", "-02.js", "test.prototype.sub.sub", 17, 0],
["t", "-02.js", "test.prototype.sub.sub.sub", 19, 0],
["x", "-02.js", "", 19, 31],
["y", "-02.js", "", 19, 40],
["z", "-02.js", "", 19, 49]
]
for (let [label, value, description, line, col] of expectedResults) {
is(gFilteredFunctions.selectedItem.label,
gDebugger.SourceUtils.trimUrlLength(label + "()"),
"The corect label (" + label + ") is currently selected.");
ok(gFilteredFunctions.selectedItem.value.contains(value),
"The corect value (" + value + ") is attached.");
is(gFilteredFunctions.selectedItem.description, description,
"The corect description (" + description + ") is currently shown.");
info("Editor caret position: " + gEditor.getCaretPosition().toSource());
ok(gEditor.getCaretPosition().line == line &&
gEditor.getCaretPosition().col == col,
"The editor didn't jump to the correct line.");
ok(gSources.selectedLabel, label,
"The current source isn't the correct one, according to the label.");
ok(gSources.selectedValue, value,
"The current source isn't the correct one, according to the value.");
EventUtils.sendKey("DOWN", gDebugger);
}
ok(gEditor.getCaretPosition().line == expectedResults[0][3] &&
gEditor.getCaretPosition().col == expectedResults[0][4],
"The editor didn't jump to the correct line again.");
executeSoon(callback);
});
} else {
ok(false, "How did you get here? Go away, you.");
}
});
write("@");
}
function thirdSearch(callback) {
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
info("Current script url:\n" + gSources.selectedValue + "\n");
info("Debugger editor text:\n" + gEditor.getText() + "\n");
ok(gFilteredFunctions.selectedValue,
"An item should be selected in the filtered functions view");
ok(gFilteredFunctions.selectedLabel,
"An item should be selected in the filtered functions view");
let url = gSources.selectedValue;
if (url.indexOf("-03.js") != -1) {
executeSoon(function() {
let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
let expectedResults = [
["namedEventListener", "-03.js", "", 3, 42],
["a" + s + "A", "-03.js", "bar", 9, 4],
["b" + s + "B", "-03.js", "bar.alpha", 14, 4],
["c" + s + "C", "-03.js", "bar.alpha.beta", 19, 4],
["d" + s + "D", "-03.js", "theta", 24, 4],
["fun", "-03.js", "", 28, 6],
["foo", "-03.js", "", 28, 12],
["bar", "-03.js", "", 28, 18],
["t_foo", "-03.js", "", 28, 24],
["w_bar" + s + "baz", "-03.js", "window", 28, 37]
];
for (let [label, value, description, line, col] of expectedResults) {
is(gFilteredFunctions.selectedItem.label,
gDebugger.SourceUtils.trimUrlLength(label + "()"),
"The corect label (" + label + ") is currently selected.");
ok(gFilteredFunctions.selectedItem.value.contains(value),
"The corect value (" + value + ") is attached.");
is(gFilteredFunctions.selectedItem.description, description,
"The corect description (" + description + ") is currently shown.");
info("Editor caret position: " + gEditor.getCaretPosition().toSource());
ok(gEditor.getCaretPosition().line == line &&
gEditor.getCaretPosition().col == col,
"The editor didn't jump to the correct line.");
ok(gSources.selectedLabel, label,
"The current source isn't the correct one, according to the label.");
ok(gSources.selectedValue, value,
"The current source isn't the correct one, according to the value.");
EventUtils.sendKey("DOWN", gDebugger);
}
ok(gEditor.getCaretPosition().line == expectedResults[0][3] &&
gEditor.getCaretPosition().col == expectedResults[0][4],
"The editor didn't jump to the correct line again.");
executeSoon(callback);
});
} else {
ok(false, "How did you get here? Go away, you.");
}
});
write("@");
}
function filterSearch(callback) {
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
info("Current script url:\n" + gSources.selectedValue + "\n");
info("Debugger editor text:\n" + gEditor.getText() + "\n");
ok(gFilteredFunctions.selectedValue,
"An item should be selected in the filtered functions view");
ok(gFilteredFunctions.selectedLabel,
"An item should be selected in the filtered functions view");
let url = gSources.selectedValue;
if (url.indexOf("-03.js") != -1) {
executeSoon(function() {
let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
let expectedResults = [
["namedEventListener", "-03.js", "", 3, 42],
["a" + s + "A", "-03.js", "bar", 9, 4],
["bar", "-03.js", "", 28, 18],
["w_bar" + s + "baz", "-03.js", "window", 28, 37],
["test3" + s + "test3_NAME", "-02.js", "", 7, 4],
["test4_SAME_NAME", "-02.js", "", 10, 4],
["anonymousExpression", "-01.js", "test.prototype", 8, 2],
["namedExpression" + s + "NAME", "-01.js", "test.prototype", 10, 2],
["a_test", "-01.js", "foo", 21, 2],
["a_test", "-01.js", "foo.sub", 26, 4]
];
for (let [label, value, description, line, col] of expectedResults) {
is(gFilteredFunctions.selectedItem.label,
gDebugger.SourceUtils.trimUrlLength(label + "()"),
"The corect label (" + label + ") is currently selected.");
ok(gFilteredFunctions.selectedItem.value.contains(value),
"The corect value (" + value + ") is attached.");
is(gFilteredFunctions.selectedItem.description, description,
"The corect description (" + description + ") is currently shown.");
info("Editor caret position: " + gEditor.getCaretPosition().toSource());
ok(gEditor.getCaretPosition().line == line &&
gEditor.getCaretPosition().col == col,
"The editor didn't jump to the correct line.");
ok(gSources.selectedLabel, label,
"The current source isn't the correct one, according to the label.");
ok(gSources.selectedValue, value,
"The current source isn't the correct one, according to the value.");
EventUtils.sendKey("DOWN", gDebugger);
}
ok(gEditor.getCaretPosition().line == expectedResults[0][3] &&
gEditor.getCaretPosition().col == expectedResults[0][4],
"The editor didn't jump to the correct line again.");
executeSoon(callback);
});
} else {
ok(false, "How did you get here? Go away, you.");
}
});
write("@a");
}
function bogusSearch(callback) {
gDebugger.addEventListener("popuphidden", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
ok(true, "Popup was successfully hidden after no matches were found!");
executeSoon(callback);
});
write("@bogus");
}
function anotherSearch(callback) {
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
ok(true, "Popup was successfully shown after some matches were found!");
executeSoon(callback);
});
write("@NAME");
}
function emptySearch(callback) {
gDebugger.addEventListener("popuphidden", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
ok(true, "Popup was successfully hidden when nothing was searched!");
executeSoon(callback);
});
clear();
}
function showSource(label, callback) {
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
executeSoon(callback);
});
gSources.selectedLabel = label;
}
function saveSearch(callback) {
gDebugger.addEventListener("popuphidden", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
executeSoon(callback);
});
if (Math.random() >= 0.5) {
EventUtils.sendKey("RETURN", gDebugger);
} else {
EventUtils.sendMouseEvent({ type: "click" },
gFilteredFunctions.selectedItem.target,
gDebugger);
}
}
function waitForCaretPos(number, callback)
{
// Poll every few milliseconds until the source editor line is active.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
info("caret: " + gEditor.getCaretPosition().line);
if (++count > 50) {
ok(false, "Timed out while polling for the line.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if (gEditor.getCaretPosition().line != number) {
return;
}
// We got the source editor at the expected line, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
function clear() {
gSearchBox.focus();
gSearchBox.value = "";
}
function write(text) {
clear();
append(text);
}
function backspace(times) {
for (let i = 0; i < times; i++) {
EventUtils.sendKey("BACK_SPACE", gDebugger);
}
}
function append(text) {
gSearchBox.focus();
for (let i = 0; i < text.length; i++) {
EventUtils.sendChar(text[i], gDebugger);
}
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
gEditor = null;
gSources = null;
gSearchBox = null;
gFilteredFunctions = null;
});

View File

@ -109,37 +109,30 @@ function doSearch() {
function testLocationChange()
{
let viewCleared = false;
let cacheCleared = false;
function _maybeFinish() {
if (viewCleared && cacheCleared) {
closeDebuggerAndFinish();
gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
dump("tabNavigated state " + aPacket.state + "\n");
if (aPacket.state == "start") {
return;
}
}
gDebugger.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onViewCleared(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onViewCleared);
gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
is(gSearchView._container._list.childNodes.length, 0,
"The global search pane shouldn't have any child nodes after a page navigation.");
is(gSearchView._container._parent.hidden, true,
"The global search pane shouldn't be visible after a page navigation.");
is(gSearchView._splitter.hidden, true,
"The global search pane splitter shouldn't be visible after a page navigation.");
ok(true, "tabNavigated event was fired after location change.");
info("Still attached to the tab.");
viewCleared = true;
_maybeFinish();
});
executeSoon(function() {
is(gSearchView._container._list.childNodes.length, 0,
"The global search pane shouldn't have any child nodes after a page navigation.");
is(gSearchView._container._parent.hidden, true,
"The global search pane shouldn't be visible after a page navigation.");
is(gSearchView._splitter.hidden, true,
"The global search pane splitter shouldn't be visible after a page navigation.");
gDebugger.addEventListener("Debugger:GlobalSearch:CacheCleared", function _onCacheCleared(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onCacheCleared);
is(gDebugger.DebuggerController.SourceScripts.getCache().length, 0,
"The scripts sources cache for global searching should be cleared after a page navigation.")
is(gSearchView._cache.size, 0,
"The scripts sources cache for global searching should be cleared after a page navigation.")
cacheCleared = true;
_maybeFinish();
closeDebuggerAndFinish();
});
});
content.location = TAB1_URL;

View File

@ -30,7 +30,6 @@ function test()
Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0);
});
});
}
function testScriptSearching() {
@ -58,14 +57,14 @@ function firstSearch() {
gDebugger.SourceUtils.trimUrlLength(gSources.labels[i]),
"The filtered sources view should have the correct labels.");
is(gFilteredSources.values[i],
gDebugger.SourceUtils.trimUrlLength(gSources.values[i]),
gDebugger.SourceUtils.trimUrlLength(gSources.values[i], 0, "start"),
"The filtered sources view should have the correct values.");
is(gFilteredSources.visibleItems[i].label,
gDebugger.SourceUtils.trimUrlLength(gSources.labels[i]),
"The filtered sources view should have the correct labels.");
is(gFilteredSources.visibleItems[i].value,
gDebugger.SourceUtils.trimUrlLength(gSources.values[i]),
gDebugger.SourceUtils.trimUrlLength(gSources.values[i], 0, "start"),
"The filtered sources view should have the correct values.");
is(gFilteredSources.visibleItems[i].attachment.fullLabel, gSources.labels[i],
@ -75,7 +74,7 @@ function firstSearch() {
}
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -135,14 +134,14 @@ function secondSearch() {
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].label),
"The filtered sources view should have the correct labels.");
is(gFilteredSources.values[i],
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value),
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value, 0, "start"),
"The filtered sources view should have the correct values.");
is(gFilteredSources.visibleItems[i].label,
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].label),
"The filtered sources view should have the correct labels.");
is(gFilteredSources.visibleItems[i].value,
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value),
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value, 0, "start"),
"The filtered sources view should have the correct values.");
is(gFilteredSources.visibleItems[i].attachment.fullLabel, gSources.visibleItems[i].label,
@ -152,7 +151,7 @@ function secondSearch() {
}
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -175,7 +174,7 @@ function secondSearch() {
ok(false, "How did you get here?");
}
}
append("-0")
write(".-0");
}
function thirdSearch() {
@ -212,14 +211,14 @@ function thirdSearch() {
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].label),
"The filtered sources view should have the correct labels.");
is(gFilteredSources.values[i],
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value),
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value, 0, "start"),
"The filtered sources view should have the correct values.");
is(gFilteredSources.visibleItems[i].label,
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].label),
"The filtered sources view should have the correct labels.");
is(gFilteredSources.visibleItems[i].value,
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value),
gDebugger.SourceUtils.trimUrlLength(gSources.visibleItems[i].value, 0, "start"),
"The filtered sources view should have the correct values.");
is(gFilteredSources.visibleItems[i].attachment.fullLabel, gSources.visibleItems[i].label,
@ -229,7 +228,7 @@ function thirdSearch() {
}
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -252,7 +251,7 @@ function thirdSearch() {
ok(false, "How did you get here?");
}
}
backspace(1)
write(".-");
}
function goDown() {
@ -266,7 +265,7 @@ function goDown() {
"The filtered sources view should have 3 items visible.");
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -304,7 +303,7 @@ function goDownAgain() {
"The filtered sources view should have 3 items visible.");
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -342,7 +341,7 @@ function goDownAndWrap() {
"The filtered sources view should have 3 items visible.");
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -380,7 +379,7 @@ function goUpAndWrap() {
"The filtered sources view should have 3 items visible.");
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -458,7 +457,7 @@ function clickAndSwitch() {
"The filtered sources view should have 3 items visible.");
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -485,12 +484,12 @@ function clickAndSwitch() {
}
});
ok(gFilteredSources._container._parent.querySelectorAll(".dbg-source-item")[0]
.classList.contains("dbg-source-item"),
ok(gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[0]
.classList.contains("results-panel-item"),
"The first visible item target isn't the correct one.");
EventUtils.sendMouseEvent({ type: "click" },
gFilteredSources._container._parent.querySelector(".dbg-source-item"),
gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[0],
gDebugger);
}
@ -545,7 +544,7 @@ function clickAndSwitchAgain() {
"The filtered sources view should have 3 items visible.");
is(gFilteredSources.selectedValue,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue),
gDebugger.SourceUtils.trimUrlLength(gSources.selectedValue, 0, "start"),
"The correct item should be selected in the filtered sources view");
is(gFilteredSources.selectedLabel,
gDebugger.SourceUtils.trimUrlLength(gSources.selectedLabel),
@ -572,12 +571,12 @@ function clickAndSwitchAgain() {
}
});
ok(gFilteredSources._container._parent.querySelectorAll(".dbg-source-item")[2]
.classList.contains("dbg-source-item"),
ok(gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[2]
.classList.contains("results-panel-item"),
"The first visible item target isn't the correct one.");
EventUtils.sendMouseEvent({ type: "click" },
gFilteredSources._container._parent.querySelectorAll(".dbg-source-item")[2],
gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[2],
gDebugger);
}
@ -672,12 +671,6 @@ function write(text) {
append(text);
}
function backspace(times) {
for (let i = 0; i < times; i++) {
EventUtils.sendKey("BACK_SPACE", gDebugger);
}
}
function append(text) {
gSearchBox.focus();

View File

@ -0,0 +1,201 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TAB_URL = EXAMPLE_URL + "browser_dbg_function-search-01.html";
/**
* Tests if the sources cache knows how to cache sources when prompted.
*/
let gPane = null;
let gTab = null;
let gDebuggee = null;
let gDebugger = null;
let gEditor = null;
let gSources = null;
let gControllerSources = null;
let gPrevLabelsCache = null;
let gPrevGroupsCache = null;
const TOTAL_SOURCES = 3;
requestLongerTimeout(2);
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
Services.tm.currentThread.dispatch({ run: testSourcesCache }, 0);
});
});
}
function testSourcesCache()
{
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
gControllerSources = gDebugger.DebuggerController.SourceScripts;
ok(gEditor.getText().contains("First source!"),
"Editor text contents appears to be correct.");
is(gSources.selectedLabel, "test-function-search-01.js",
"The currently selected label in the sources container is correct.");
ok(gSources.selectedValue.contains("test-function-search-01.js"),
"The currently selected value in the sources container appears to be correct.");
is(gSources.itemCount, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " sources present in the sources list.");
is(gSources.visibleItems.length, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " sources visible in the sources list.");
is(gSources.labels.length, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " labels stored in the sources container model.")
is(gSources.values.length, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " values stored in the sources container model.")
info("Source labels: " + gSources.labels.toSource());
info("Source values: " + gSources.values.toSource());
is(gSources.labels.sort()[0], "test-function-search-01.js",
"The first source label is correct.");
ok(gSources.values.sort()[0].contains("test-function-search-01.js"),
"The first source value appears to be correct.");
is(gSources.labels.sort()[1], "test-function-search-02.js",
"The second source label is correct.");
ok(gSources.values.sort()[1].contains("test-function-search-02.js"),
"The second source value appears to be correct.");
is(gSources.labels.sort()[2], "test-function-search-03.js",
"The third source label is correct.");
ok(gSources.values.sort()[2].contains("test-function-search-03.js"),
"The third source value appears to be correct.");
is(gControllerSources.getCache().length, 0,
"The sources cache should be empty when debugger starts.");
is(gDebugger.SourceUtils._labelsCache.size, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " labels cached");
is(gDebugger.SourceUtils._groupsCache.size, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " groups cached");
gPrevLabelsCache = gDebugger.SourceUtils._labelsCache;
gPrevLabelsCache = gDebugger.SourceUtils._groupsCache;
fetchSources(function() {
performReload(function() {
closeDebuggerAndFinish();
});
});
}
function fetchSources(callback) {
let fetches = 0;
let timeouts = 0;
gControllerSources.fetchSources(gSources.values, {
onFetch: function(aSource) {
info("Fetched: " + aSource.url);
fetches++;
},
onTimeout: function(aSource) {
info("Timed out: " + aSource.url);
timeouts++;
},
onFinished: function() {
info("Finished...");
ok(fetches > 0,
"At least one source should have been fetched.");
is(fetches + timeouts, TOTAL_SOURCES,
"The correct number of sources have been either fetched or timed out.");
let cache = gControllerSources.getCache();
is(cache.length, fetches,
"The sources cache should have exactly " + fetches + " sources cached.");
testCacheIntegrity();
callback();
}
});
}
function performReload(callback) {
gDebugger.DebuggerController.client.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
dump("tabNavigated state " + aPacket.state + "\n");
if (aPacket.state == "start") {
testStateBeforeReload();
return;
}
gDebugger.DebuggerController.client.removeListener("tabNavigated", onTabNavigated);
ok(true, "tabNavigated event was fired.");
info("Still attached to the tab.");
testStateAfterReload();
callback();
});
gDebuggee.location.reload();
}
function testStateBeforeReload() {
is(gSources.itemCount, 0,
"There should be no sources present in the sources list during reload.");
is(gControllerSources.getCache().length, 0,
"The sources cache should be empty during reload.");
isnot(gDebugger.SourceUtils._labelsCache, gPrevLabelsCache,
"The labels cache has been refreshed during reload.")
isnot(gDebugger.SourceUtils._groupsCache, gPrevGroupsCache,
"The groups cache has been refreshed during reload.")
is(gDebugger.SourceUtils._labelsCache.size, 0,
"There should be no labels cached during reload");
is(gDebugger.SourceUtils._groupsCache.size, 0,
"There should be no groups cached during reload");
}
function testStateAfterReload() {
is(gSources.itemCount, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " sources present in the sources list.");
is(gDebugger.SourceUtils._labelsCache.size, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " labels cached after reload.");
is(gDebugger.SourceUtils._groupsCache.size, TOTAL_SOURCES,
"There should be " + TOTAL_SOURCES + " groups cached after reload.");
}
function testCacheIntegrity() {
let cache = gControllerSources.getCache();
isnot(cache.length, 0,
"The sources cache should not be empty at this point.");
for (let source of cache) {
let index = cache.indexOf(source);
ok(source[0].contains("test-function-search-0" + (index + 1)),
"Found a source url cached correctly (" + index + ")");
ok(source[1].contains(
["First source!", "Second source!", "Third source!"][index]),
"Found a source's text contents cached correctly (" + index + ")");
info("Cached source url at " + index + ": " + source[0]);
info("Cached source text at " + index + ": " + source[1]);
}
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
gEditor = null;
gSources = null;
gControllerSources = null;
gPrevLabelsCache = null;
gPrevGroupsCache = null;
});

View File

@ -0,0 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
// Blah! First source!
}
test.prototype = {
anonymousExpression: function() {
},
namedExpression: function NAME() {
},
sub: {
sub: {
sub: {
}
}
}
};
var foo = {
a_test: function() {
},
n_test: function x() {
},
sub: {
a_test: function() {
},
n_test: function y() {
},
sub: {
a_test: function() {
},
n_test: function z() {
},
sub: {
test_SAME_NAME: function test_SAME_NAME() {
}
}
}
}
};

View File

@ -0,0 +1,21 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var test2 = function() {
// Blah! Second source!
}
var test3 = function test3_NAME() {
}
var test4_SAME_NAME = function test4_SAME_NAME() {
}
test.prototype.x = function X() {
};
test.prototype.sub.y = function Y() {
};
test.prototype.sub.sub.z = function Z() {
};
test.prototype.sub.sub.sub.t = this.x = this.y = this.z = function() {
};

View File

@ -0,0 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
window.addEventListener("bogus", function namedEventListener() {
// Blah! Third source!
});
try {
var bar = foo.sub.sub.test({
a: function A() {
}
});
bar.alpha = foo.sub.sub.test({
b: function B() {
}
});
bar.alpha.beta = new X(Y(Z(foo.sub.sub.test({
c: function C() {
}
}))));
this.theta = new X(new Y(new Z(new foo.sub.sub.test({
d: function D() {
}
}))));
var fun = foo = bar = this.t_foo = window.w_bar = function baz() {};
} catch (e) {
}

File diff suppressed because it is too large Load Diff

View File

@ -73,12 +73,19 @@
- in the source editor's context menu for the scripts search operation. -->
<!ENTITY debuggerUI.searchFile "Filter scripts">
<!ENTITY debuggerUI.searchFile.key "P">
<!ENTITY debuggerUI.searchFile.altkey "O">
<!-- LOCALIZATION NOTE (debuggerUI.searchGlobal): This is the text that appears
- in the source editor's context menu for the global search operation. -->
<!ENTITY debuggerUI.searchGlobal "Search in all files">
<!ENTITY debuggerUI.searchGlobal.key "F">
<!-- LOCALIZATION NOTE (debuggerUI.searchFunction): This is the text that appears
- in the source editor's context menu for the function search operation. -->
<!ENTITY debuggerUI.searchFunction "Search for function definition">
<!ENTITY debuggerUI.searchFunction.key "D">
<!ENTITY debuggerUI.searchFunction.altkey "O">
<!-- LOCALIZATION NOTE (debuggerUI.searchToken): This is the text that appears
- in the source editor's context menu for the token search operation. -->
<!ENTITY debuggerUI.searchToken "Find">

View File

@ -120,6 +120,10 @@ emptyVariablesFilterText=Filter variables
# filter panel popup for the global search operation.
searchPanelGlobal=Search in all files (%S)
# LOCALIZATION NOTE (searchPanelFunction): This is the text that appears in the
# filter panel popup for the function search operation.
searchPanelFunction=Search for function definition (%S)
# LOCALIZATION NOTE (searchPanelToken): This is the text that appears in the
# filter panel popup for the token search operation.
searchPanelToken=Find in this file (%S)
@ -206,3 +210,8 @@ variablesSeparatorLabel=:
# LOCALIZATION NOTE (watchExpressionsSeparatorLabel): The text that is displayed
# in the watch expressions list as a separator between the code and evaluation.
watchExpressionsSeparatorLabel=\ →
# LOCALIZATION NOTE (functionSearchSeparatorLabel): The text that is displayed
# in the functions search panel as a separator between function's inferred name
# and its real name (if available).
functionSearchSeparatorLabel=

View File

@ -138,14 +138,14 @@
padding-bottom: 2px;
}
/* Filtering results panel */
/* Searchbox results panel */
#filtered-sources-panel {
.results-panel {
padding: 4px;
opacity: 0.9;
}
.dbg-source-item {
.results-panel-item {
background: #f4f4f4;
border: 1px solid #ddd;
border-top-color: #fff;
@ -153,31 +153,37 @@
cursor: pointer;
}
.dbg-source-item:first-of-type {
.results-panel-item:first-of-type {
border-top-color: #ddd;
border-radius: 4px 4px 0 0;
}
.dbg-source-item:last-of-type {
.results-panel-item:last-of-type {
border-radius: 0 0 4px 4px;
}
.dbg-source-item:only-of-type {
.results-panel-item:only-of-type {
border-radius: 4px;
}
.dbg-source-item:not(.selected):not(:hover) {
.results-panel-item:not(.selected):not(:hover) {
text-shadow: 0 1px #fff;
}
.dbg-source-item-name {
.results-panel-item-pre {
-moz-margin-end: 5px !important;
color: #444;
cursor: inherit;
}
.results-panel-item-name {
color: #111;
font-weight: 600;
cursor: inherit;
}
.dbg-source-item-details {
color: #777;
.results-panel-item-details {
color: #7f7f7f;
cursor: inherit;
}

View File

@ -140,14 +140,14 @@
padding-bottom: 2px;
}
/* Filtering results panel */
/* Searchbox results panel */
#filtered-sources-panel {
.results-panel {
padding: 4px;
opacity: 0.9;
}
.dbg-source-item {
.results-panel-item {
background: #f4f4f4;
border: 1px solid #ddd;
border-top-color: #fff;
@ -155,31 +155,37 @@
cursor: pointer;
}
.dbg-source-item:first-of-type {
.results-panel-item:first-of-type {
border-top-color: #ddd;
border-radius: 4px 4px 0 0;
}
.dbg-source-item:last-of-type {
.results-panel-item:last-of-type {
border-radius: 0 0 4px 4px;
}
.dbg-source-item:only-of-type {
.results-panel-item:only-of-type {
border-radius: 4px;
}
.dbg-source-item:not(.selected):not(:hover) {
.results-panel-item:not(.selected):not(:hover) {
text-shadow: 0 1px #fff;
}
.dbg-source-item-name {
.results-panel-item-pre {
-moz-margin-end: 5px !important;
color: #444;
cursor: inherit;
}
.results-panel-item-name {
color: #111;
font-weight: 600;
cursor: inherit;
}
.dbg-source-item-details {
color: #777;
.results-panel-item-details {
color: #7f7f7f;
cursor: inherit;
}

View File

@ -138,14 +138,14 @@
padding-bottom: 2px;
}
/* Filtering results panel */
/* Searchbox results panel */
#filtered-sources-panel {
.results-panel {
padding: 4px;
opacity: 0.9;
}
.dbg-source-item {
.results-panel-item {
background: #f4f4f4;
border: 1px solid #ddd;
border-top-color: #fff;
@ -153,31 +153,37 @@
cursor: pointer;
}
.dbg-source-item:first-of-type {
.results-panel-item:first-of-type {
border-top-color: #ddd;
border-radius: 4px 4px 0 0;
}
.dbg-source-item:last-of-type {
.results-panel-item:last-of-type {
border-radius: 0 0 4px 4px;
}
.dbg-source-item:only-of-type {
.results-panel-item:only-of-type {
border-radius: 4px;
}
.dbg-source-item:not(.selected):not(:hover) {
.results-panel-item:not(.selected):not(:hover) {
text-shadow: 0 1px #fff;
}
.dbg-source-item-name {
.results-panel-item-pre {
-moz-margin-end: 5px !important;
color: #444;
cursor: inherit;
}
.results-panel-item-name {
color: #111;
font-weight: 600;
cursor: inherit;
}
.dbg-source-item-details {
color: #777;
.results-panel-item-details {
color: #7f7f7f;
cursor: inherit;
}