зеркало из https://github.com/mozilla/gecko-dev.git
Bug 558287: Add support for searching add-ons on AMO via the search bar. r=dtownsend
This commit is contained in:
Родитель
0b374032b3
Коммит
48b38e2c5e
|
@ -56,6 +56,10 @@ pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindow.xul
|
|||
// Enables some extra Extension System Logging (can reduce performance)
|
||||
pref("extensions.logging.enabled", false);
|
||||
|
||||
// Preferences for the Get Add-ons pane
|
||||
pref("extensions.getAddons.maxResults", 15);
|
||||
pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%");
|
||||
|
||||
// Preferences for AMO integration
|
||||
pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/%APP%/discovery/%VERSION%/%OS%");
|
||||
|
||||
|
|
|
@ -122,3 +122,7 @@ XPCOMUtils.defineLazyServiceGetter(Services, "console",
|
|||
XPCOMUtils.defineLazyServiceGetter(Services, "strings",
|
||||
"@mozilla.org/intl/stringbundle;1",
|
||||
"nsIStringBundleService");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(Services, "urlFormatter",
|
||||
"@mozilla.org/toolkit/URLFormatterService;1",
|
||||
"nsIURLFormatter");
|
||||
|
|
|
@ -66,4 +66,5 @@ function checkServices() {
|
|||
checkService("ww", Ci.nsIWindowWatcher);
|
||||
checkService("tm", Ci.nsIThreadManager);
|
||||
checkService("strings", Ci.nsIStringBundleService);
|
||||
checkService("urlFormatter", Ci.nsIURLFormatter);
|
||||
}
|
||||
|
|
|
@ -38,9 +38,9 @@
|
|||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = [ "AddonRepository" ];
|
||||
|
@ -227,9 +227,8 @@ var AddonRepository = {
|
|||
* string.
|
||||
*/
|
||||
get homepageURL() {
|
||||
return Cc["@mozilla.org/toolkit/URLFormatterService;1"].
|
||||
getService(Ci.nsIURLFormatter).
|
||||
formatURLPref(PREF_GETADDONS_BROWSEADDONS);
|
||||
var url = this._formatURLPref(PREF_GETADDONS_BROWSEADDONS, {});
|
||||
return (url != null) ? url : "about:blank";
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -244,10 +243,8 @@ var AddonRepository = {
|
|||
* The url that can be visited to see recommended add-ons in this repository.
|
||||
*/
|
||||
getRecommendedURL: function() {
|
||||
var urlf = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
|
||||
getService(Ci.nsIURLFormatter);
|
||||
|
||||
return urlf.formatURLPref(PREF_GETADDONS_BROWSERECOMMENDED);
|
||||
var url = this._formatURLPref(PREF_GETADDONS_BROWSERECOMMENDED, {});
|
||||
return (url != null) ? url : "about:blank";
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -257,14 +254,10 @@ var AddonRepository = {
|
|||
* @param aSearchTerms search terms used to search the repository
|
||||
*/
|
||||
getSearchURL: function(aSearchTerms) {
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch);
|
||||
var urlf = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
|
||||
getService(Ci.nsIURLFormatter);
|
||||
|
||||
var url = prefs.getCharPref(PREF_GETADDONS_BROWSESEARCHRESULTS);
|
||||
url = url.replace(/%TERMS%/g, encodeURIComponent(aSearchTerms));
|
||||
return urlf.formatURL(url);
|
||||
var url = this._formatURLPref(PREF_GETADDONS_BROWSESEARCHRESULTS, {
|
||||
TERMS : encodeURIComponent(aSearchTerms)
|
||||
});
|
||||
return (url != null) ? url : "about:blank";
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -298,15 +291,14 @@ var AddonRepository = {
|
|||
this._recommended = true;
|
||||
this._maxResults = aMaxResults;
|
||||
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch);
|
||||
var urlf = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
|
||||
getService(Ci.nsIURLFormatter);
|
||||
var url = this._formatURLPref(PREF_GETADDONS_GETRECOMMENDED, {
|
||||
API_VERSION : API_VERSION,
|
||||
|
||||
var uri = prefs.getCharPref(PREF_GETADDONS_GETRECOMMENDED);
|
||||
uri = uri.replace(/%API_VERSION%/g, API_VERSION);
|
||||
uri = urlf.formatURL(uri);
|
||||
this._loadList(uri);
|
||||
// Get twice as many results to account for potential filtering
|
||||
MAX_RESULTS : 2 * aMaxResults
|
||||
});
|
||||
|
||||
(url != null) ? this._loadList(url) : this.reportFailure();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -327,17 +319,18 @@ var AddonRepository = {
|
|||
this._recommended = false;
|
||||
this._maxResults = aMaxResults;
|
||||
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch);
|
||||
var urlf = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
|
||||
getService(Ci.nsIURLFormatter);
|
||||
// Get twice as many results to account for potential filtering
|
||||
var url = this._formatURLPref(PREF_GETADDONS_GETSEARCHRESULTS, {
|
||||
API_VERSION : API_VERSION,
|
||||
|
||||
var uri = prefs.getCharPref(PREF_GETADDONS_GETSEARCHRESULTS);
|
||||
uri = uri.replace(/%API_VERSION%/g, API_VERSION);
|
||||
// We double encode due to bug 427155
|
||||
uri = uri.replace(/%TERMS%/g, encodeURIComponent(encodeURIComponent(aSearchTerms)));
|
||||
uri = urlf.formatURL(uri);
|
||||
this._loadList(uri);
|
||||
// Get twice as many results to account for potential filtering
|
||||
MAX_RESULTS : 2 * aMaxResults,
|
||||
|
||||
// We double encode due to bug 427155
|
||||
TERMS : encodeURIComponent(encodeURIComponent(aSearchTerms))
|
||||
});
|
||||
|
||||
(url != null) ? this._loadList(url) : this.reportFailure();
|
||||
},
|
||||
|
||||
// Posts results to the callback
|
||||
|
@ -365,10 +358,6 @@ var AddonRepository = {
|
|||
|
||||
// Parses an add-on entry from an <addon> element
|
||||
_parseAddon: function(aElement, aSkip) {
|
||||
var app = Cc["@mozilla.org/xre/app-info;1"].
|
||||
getService(Ci.nsIXULAppInfo).
|
||||
QueryInterface(Ci.nsIXULRuntime);
|
||||
|
||||
var guidList = aElement.getElementsByTagName("guid");
|
||||
if (guidList.length != 1)
|
||||
return;
|
||||
|
@ -399,7 +388,7 @@ var AddonRepository = {
|
|||
var i = 0;
|
||||
while (i < osList.length && !compatible) {
|
||||
var os = osList[i].textContent.trim();
|
||||
if (os == "ALL" || os == app.OS) {
|
||||
if (os == "ALL" || os == Services.appinfo.OS) {
|
||||
compatible = true;
|
||||
break;
|
||||
}
|
||||
|
@ -414,17 +403,16 @@ var AddonRepository = {
|
|||
var tags = aElement.getElementsByTagName("compatible_applications");
|
||||
if (tags.length != 1)
|
||||
return;
|
||||
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
|
||||
getService(Ci.nsIVersionComparator);
|
||||
|
||||
var apps = tags[0].getElementsByTagName("appID");
|
||||
var i = 0;
|
||||
while (i < apps.length) {
|
||||
if (apps[i].textContent.trim() == app.ID) {
|
||||
if (apps[i].textContent.trim() == Services.appinfo.ID) {
|
||||
var parent = apps[i].parentNode;
|
||||
var minversion = parent.getElementsByTagName("min_version")[0].textContent.trim();
|
||||
var maxversion = parent.getElementsByTagName("max_version")[0].textContent.trim();
|
||||
if ((vc.compare(minversion, app.version) > 0) ||
|
||||
(vc.compare(app.version, maxversion) > 0))
|
||||
if ((Services.vc.compare(minversion, Services.appinfo.version) > 0) ||
|
||||
(Services.vc.compare(Services.appinfo.version, maxversion) > 0))
|
||||
return;
|
||||
compatible = true;
|
||||
break;
|
||||
|
@ -480,7 +468,7 @@ var AddonRepository = {
|
|||
if (node.hasAttribute("os")) {
|
||||
var os = node.getAttribute("os").toLowerCase();
|
||||
// If the os is not ALL and not the current OS then ignore this xpi
|
||||
if (os != "all" && os != app.OS.toLowerCase())
|
||||
if (os != "all" && os != Services.appinfo.OS.toLowerCase())
|
||||
break;
|
||||
}
|
||||
result.xpiURL = node.textContent.trim();
|
||||
|
@ -579,6 +567,23 @@ var AddonRepository = {
|
|||
this._request.onerror = function(aEvent) { self._reportFailure(); };
|
||||
this._request.onload = function(aEvent) { self._listLoaded(aEvent); };
|
||||
this._request.send(null);
|
||||
},
|
||||
|
||||
// Create url from pref, returning null if pref does not exist
|
||||
_formatURLPref: function(aPref, aSubstitutions) {
|
||||
var url = null;
|
||||
try {
|
||||
url = Services.prefs.getCharPref(aPref);
|
||||
} catch(e) {
|
||||
Cu.reportError("_formatURLPref: Couldn't get pref: " + aPref);
|
||||
return null;
|
||||
}
|
||||
|
||||
url = url.replace(/%([A-Z_]+)%/g, function(aMatch, aKey) {
|
||||
return (aKey in aSubstitutions) ? aSubstitutions[aKey] : aMatch;
|
||||
});
|
||||
|
||||
return Services.urlFormatter.formatURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,18 +45,21 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||
Cu.import("resource://gre/modules/DownloadUtils.jsm");
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource://gre/modules/AddonRepository.jsm");
|
||||
|
||||
|
||||
const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
|
||||
const PREF_MAXRESULTS = "extensions.getAddons.maxResults";
|
||||
|
||||
const LOADING_MSG_DELAY = 100;
|
||||
|
||||
const SEARCH_SCORE_MULTIPLIER_NAME = 2;
|
||||
const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2;
|
||||
|
||||
const SEARCH_SCORE_MATCH_WHOLEWORD = 1;
|
||||
const SEARCH_SCORE_MATCH_WORDBOUNDRY = 0.6;
|
||||
const SEARCH_SCORE_MATCH_SUBSTRING = 0.3;
|
||||
// Use integers so search scores are sortable by nsIXULSortService
|
||||
const SEARCH_SCORE_MATCH_WHOLEWORD = 10;
|
||||
const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6;
|
||||
const SEARCH_SCORE_MATCH_SUBSTRING = 3;
|
||||
|
||||
const VIEW_DEFAULT = "addons://list/extension";
|
||||
|
||||
|
@ -124,6 +127,7 @@ function notifyInitialized() {
|
|||
}
|
||||
|
||||
function shutdown() {
|
||||
gSearchView.shutdown();
|
||||
gEventManager.shutdown();
|
||||
gViewController.shutdown();
|
||||
}
|
||||
|
@ -600,12 +604,13 @@ function isPending(aAddon, aAction) {
|
|||
}
|
||||
|
||||
|
||||
function createItem(aObj, aIsInstall, aRequiresRestart) {
|
||||
function createItem(aObj, aIsInstall, aRequiresRestart, aIsRemote) {
|
||||
let item = document.createElement("richlistitem");
|
||||
|
||||
item.setAttribute("class", "addon");
|
||||
item.setAttribute("name", aObj.name);
|
||||
item.setAttribute("type", aObj.type);
|
||||
item.setAttribute("remote", !!aIsRemote);
|
||||
|
||||
if (aIsInstall) {
|
||||
item.mInstall = aObj;
|
||||
|
@ -788,6 +793,7 @@ var gCategories = {
|
|||
|
||||
var gHeader = {
|
||||
_search: null,
|
||||
_searching: null,
|
||||
_name: null,
|
||||
_link: null,
|
||||
_dest: "",
|
||||
|
@ -796,6 +802,7 @@ var gHeader = {
|
|||
this._name = document.getElementById("header-name");
|
||||
this._link = document.getElementById("header-link");
|
||||
this._search = document.getElementById("header-search");
|
||||
this._searching = document.getElementById("header-searching");
|
||||
|
||||
var self = this;
|
||||
this._link.addEventListener("command", function() {
|
||||
|
@ -804,6 +811,9 @@ var gHeader = {
|
|||
|
||||
this._search.addEventListener("command", function(aEvent) {
|
||||
var query = aEvent.target.value;
|
||||
if (query.length == 0)
|
||||
return false;
|
||||
|
||||
gViewController.loadView("addons://search/" + encodeURIComponent(query));
|
||||
}, false);
|
||||
|
||||
|
@ -829,6 +839,17 @@ var gHeader = {
|
|||
|
||||
set searchQuery(aQuery) {
|
||||
this._search.value = aQuery;
|
||||
},
|
||||
|
||||
get isSearching() {
|
||||
return this._searching.hasAttribute("active");
|
||||
},
|
||||
|
||||
set isSearching(aIsSearching) {
|
||||
if (aIsSearching)
|
||||
this._searching.setAttribute("active", true);
|
||||
else
|
||||
this._searching.removeAttribute("active");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -888,14 +909,22 @@ var gDiscoverView = {
|
|||
};
|
||||
|
||||
|
||||
var gCachedAddons = {};
|
||||
|
||||
var gSearchView = {
|
||||
node: null,
|
||||
_localFilter: null,
|
||||
_remoteFilter: null,
|
||||
_sorters: null,
|
||||
_listBox: null,
|
||||
_emptyNotice: null,
|
||||
_lastQuery: null,
|
||||
_pendingSearches: 0,
|
||||
|
||||
initialize: function() {
|
||||
this.node = document.getElementById("search-view");
|
||||
this._localFilter = document.getElementById("search-filter-local");
|
||||
this._remoteFilter = document.getElementById("search-filter-remote");
|
||||
this._sorters = document.getElementById("search-sorters");
|
||||
this._sorters.handler = this;
|
||||
this._listBox = document.getElementById("search-list");
|
||||
|
@ -910,56 +939,151 @@ var gSearchView = {
|
|||
item.showInDetailView();
|
||||
}
|
||||
}, false);
|
||||
|
||||
this._localFilter.addEventListener("command", function() self.updateView(), false);
|
||||
this._remoteFilter.addEventListener("command", function() self.updateView(), false);
|
||||
},
|
||||
|
||||
shutdown: function() {
|
||||
// Force persist of checked state. See bug 15232
|
||||
this._localFilter.setAttribute("checked", !!this._localFilter.checked);
|
||||
this._remoteFilter.setAttribute("checked", !!this._remoteFilter.checked);
|
||||
|
||||
if (AddonRepository.isSearching)
|
||||
AddonRepository.cancelSearch();
|
||||
},
|
||||
|
||||
get isSearching() {
|
||||
return this._pendingSearches > 0;
|
||||
},
|
||||
|
||||
show: function(aQuery, aRequest) {
|
||||
gHeader.setName(gStrings.ext.GetStringFromName("header-search"));
|
||||
gHeader.isSearching = true;
|
||||
this.showEmptyNotice(false);
|
||||
|
||||
gHeader.searchQuery = aQuery;
|
||||
aQuery = aQuery.trim().toLocaleLowerCase();
|
||||
if (this._lastQuery == aQuery) {
|
||||
this.updateView();
|
||||
gViewController.notifyViewChanged();
|
||||
return;
|
||||
}
|
||||
this._lastQuery = aQuery;
|
||||
|
||||
if (AddonRepository.isSearching)
|
||||
AddonRepository.cancelSearch();
|
||||
|
||||
while (this._listBox.lastChild.localName == "richlistitem")
|
||||
this._listBox.removeChild(this._listBox.lastChild);
|
||||
|
||||
var self = this;
|
||||
AddonManager.getAddonsByTypes(null, function(aAddonsList) {
|
||||
gCachedAddons = {};
|
||||
this._pendingSearches = 2;
|
||||
this._sorters.setSort("relevancescore", false);
|
||||
|
||||
function createSearchResults(aObjsList, aIsInstall, aIsRemote) {
|
||||
var createdCount = 0;
|
||||
aObjsList.forEach(function(aObj) {
|
||||
let score = 0;
|
||||
if (aQuery.length > 0) {
|
||||
score = self.getMatchScore(aObj, aQuery);
|
||||
if (score == 0 && !aIsRemote)
|
||||
return;
|
||||
}
|
||||
|
||||
let item = createItem(aObj, aIsInstall, false, aIsRemote);
|
||||
item.setAttribute("relevancescore", score);
|
||||
if (aIsRemote)
|
||||
gCachedAddons[aObj.id] = aObj;
|
||||
|
||||
self._listBox.appendChild(item);
|
||||
createdCount++;
|
||||
});
|
||||
|
||||
return createdCount;
|
||||
}
|
||||
|
||||
function finishSearch(createdCount) {
|
||||
if (createdCount > 0)
|
||||
self.onSortChanged(self._sorters.sortBy, self._sorters.ascending);
|
||||
|
||||
self._pendingSearches--;
|
||||
self.updateView();
|
||||
|
||||
if (!self.isSearching)
|
||||
gViewController.notifyViewChanged();
|
||||
}
|
||||
|
||||
getAddonsAndInstalls(null, function(aAddons, aInstalls) {
|
||||
if (gViewController && aRequest != gViewController.currentViewRequest)
|
||||
return;
|
||||
|
||||
var elementCount = 0;
|
||||
for (let i = 0; i < aAddonsList.length; i++) {
|
||||
let addon = aAddonsList[i];
|
||||
let score = 0;
|
||||
if (aQuery.length > 0) {
|
||||
score = self.getMatchScore(addon, aQuery);
|
||||
if (score == 0)
|
||||
continue;
|
||||
}
|
||||
var createdCount = createSearchResults(aAddons, false, false);
|
||||
createdCount += createSearchResults(aInstalls, true, false);
|
||||
finishSearch(createdCount);
|
||||
});
|
||||
|
||||
let item = createItem(addon);
|
||||
item.setAttribute("relevancescore", score);
|
||||
self._listBox.appendChild(item);
|
||||
elementCount++;
|
||||
var maxRemoteResults = 0;
|
||||
try {
|
||||
maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS);
|
||||
} catch(e) {}
|
||||
|
||||
if (maxRemoteResults <= 0) {
|
||||
finishSearch(0);
|
||||
return;
|
||||
}
|
||||
|
||||
AddonRepository.searchAddons(aQuery, maxRemoteResults, {
|
||||
searchFailed: function() {
|
||||
if (gViewController && aRequest != gViewController.currentViewRequest)
|
||||
return;
|
||||
|
||||
// XXXunf Better handling of AMO search failure. See bug 579502
|
||||
finishSearch(0); // Silently fail
|
||||
},
|
||||
|
||||
searchSucceeded: function(aAddonsList, aAddonCount, aTotalResults) {
|
||||
if (gViewController && aRequest != gViewController.currentViewRequest)
|
||||
return;
|
||||
|
||||
var createdCount = createSearchResults(aAddonsList, false, true);
|
||||
finishSearch(createdCount);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateView: function() {
|
||||
var showLocal = this._localFilter.checked;
|
||||
var showRemote = this._remoteFilter.checked;
|
||||
this._listBox.setAttribute("local", showLocal);
|
||||
this._listBox.setAttribute("remote", showRemote);
|
||||
|
||||
gHeader.isSearching = this.isSearching;
|
||||
if (!this.isSearching) {
|
||||
var isEmpty = true;
|
||||
var results = this._listBox.getElementsByTagName("richlistitem");
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
var isRemote = (results[i].getAttribute("remote") == "true");
|
||||
if ((isRemote && showRemote) || (!isRemote && showLocal)) {
|
||||
isEmpty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (elementCount > 0)
|
||||
self.onSortChanged("relevancescore", false);
|
||||
else
|
||||
self.showEmptyNotice(true);
|
||||
this.showEmptyNotice(isEmpty);
|
||||
}
|
||||
|
||||
gViewController.updateCommands();
|
||||
gViewController.notifyViewChanged();
|
||||
});
|
||||
gViewController.updateCommands();
|
||||
},
|
||||
|
||||
hide: function() { },
|
||||
|
||||
getMatchScore: function(aAddon, aQuery) {
|
||||
getMatchScore: function(aObj, aQuery) {
|
||||
var score = 0;
|
||||
score += this.calculateMatchScore(aAddon.name, aQuery,
|
||||
score += this.calculateMatchScore(aObj.name, aQuery,
|
||||
SEARCH_SCORE_MULTIPLIER_NAME);
|
||||
score += this.calculateMatchScore(aAddon.description, aQuery,
|
||||
score += this.calculateMatchScore(aObj.description, aQuery,
|
||||
SEARCH_SCORE_MULTIPLIER_DESCRIPTION);
|
||||
return score;
|
||||
},
|
||||
|
@ -1182,6 +1306,9 @@ var gDetailView = {
|
|||
if (gViewController && aRequest != gViewController.currentViewRequest)
|
||||
return;
|
||||
|
||||
if (!aAddon && (aAddonId in gCachedAddons))
|
||||
aAddon = gCachedAddons[aAddonId];
|
||||
|
||||
self._addon = aAddon;
|
||||
gEventManager.registerAddonListener(self, aAddon.id);
|
||||
|
||||
|
@ -1206,7 +1333,8 @@ var gDetailView = {
|
|||
dateUpdated.hidden = !aAddon.updateDate;
|
||||
|
||||
var desc = document.getElementById("detail-desc");
|
||||
desc.textContent = aAddon.description;
|
||||
desc.textContent = aAddon.fullDescription ? aAddon.fullDescription
|
||||
: aAddon.description;
|
||||
|
||||
document.getElementById("detail-autoUpdate").checked = aAddon.applyBackgroundUpdates;
|
||||
var canUpdate = hasPermission(aAddon, "upgrade");
|
||||
|
|
|
@ -273,39 +273,58 @@
|
|||
return this.getAttribute("sortby");
|
||||
]]></getter>
|
||||
<setter><![CDATA[
|
||||
this.setAttribute("sortby", val);
|
||||
this._refreshState();
|
||||
if (val != this.sortBy) {
|
||||
this.setAttribute("sortBy", val);
|
||||
this._refreshState();
|
||||
}
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<property name="ascending">
|
||||
<getter><![CDATA[
|
||||
return this.hasAttribute("ascending");
|
||||
return (this.getAttribute("ascending") == "true");
|
||||
]]></getter>
|
||||
<setter><![CDATA[
|
||||
if (val)
|
||||
this.setAttribute("ascending", true);
|
||||
else
|
||||
this.removeAttribute("ascending");
|
||||
this._refreshState();
|
||||
val = !!val;
|
||||
if (val != this.ascending) {
|
||||
this.setAttribute("ascending", val);
|
||||
this._refreshState();
|
||||
}
|
||||
]]></setter>
|
||||
</property>
|
||||
|
||||
<method name="setSort">
|
||||
<parameter name="aSort"/>
|
||||
<parameter name="aAscending"/>
|
||||
<body><![CDATA[
|
||||
var sortChanged = false;
|
||||
if (aSort != this.sortBy) {
|
||||
this.setAttribute("sortby", aSort);
|
||||
sortChanged = true;
|
||||
}
|
||||
|
||||
aAscending = !!aAscending;
|
||||
if (this.ascending != aAscending) {
|
||||
this.setAttribute("ascending", aAscending);
|
||||
sortChanged = true;
|
||||
}
|
||||
|
||||
if (sortChanged)
|
||||
this._refreshState();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_handleChange">
|
||||
<parameter name="aSort"/>
|
||||
<body><![CDATA[
|
||||
const ASCENDING_SORT_FIELDS = ["name"];
|
||||
|
||||
if (aSort == this.sortBy) {
|
||||
// Toggle ascending if sort by is not changing, otherwise
|
||||
// name sorting defaults to ascending, others to descending
|
||||
if (aSort == this.sortBy)
|
||||
this.ascending = !this.ascending;
|
||||
return;
|
||||
}
|
||||
|
||||
this.sortBy = aSort;
|
||||
// Name sorting defaults to ascending, others to descending
|
||||
this.ascending = ASCENDING_SORT_FIELDS.indexOf(aSort) >= 0;
|
||||
|
||||
this._refreshState();
|
||||
else
|
||||
this.setSort(aSort, ASCENDING_SORT_FIELDS.indexOf(aSort) >= 0);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
@ -442,6 +461,10 @@
|
|||
<content>
|
||||
<xul:label anonid="message"/>
|
||||
<xul:progressmeter anonid="progress" class="download-progress"/>
|
||||
<xul:button anonid="install-remote" hidden="true"
|
||||
class="addon-control" label="&addon.install.label;"
|
||||
tooltiptext="&addon.install.tooltip;"
|
||||
oncommand="document.getBindingParent(this).installRemote();"/>
|
||||
<xul:button anonid="restart-needed" hidden="true" command="cmd_restartApp"
|
||||
class="addon-control" label="&addon.restartNow.label;"
|
||||
tooltiptext="&addon.restartNow.tooltip;"/>
|
||||
|
@ -476,6 +499,10 @@
|
|||
<field name="_progress">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "progress");
|
||||
</field>
|
||||
<field name="_installRemote">
|
||||
document.getAnonymousElementByAttribute(this, "anonid",
|
||||
"install-remote");
|
||||
</field>
|
||||
<field name="_restartNeeded">
|
||||
document.getAnonymousElementByAttribute(this, "anonid",
|
||||
"restart-needed");
|
||||
|
@ -504,6 +531,7 @@
|
|||
|
||||
<method name="refreshState">
|
||||
<body><![CDATA[
|
||||
var showInstallRemote = false;
|
||||
var showRestartInstall = false;
|
||||
var showRestartNeeded = false;
|
||||
var showUndo = false;
|
||||
|
@ -512,6 +540,11 @@
|
|||
|
||||
switch (this.mInstall.state) {
|
||||
case AddonManager.STATE_AVAILABLE:
|
||||
if (this.mControl.getAttribute("remote") != "true")
|
||||
break;
|
||||
|
||||
this._progress.hidden = true;
|
||||
showInstallRemote = true;
|
||||
break;
|
||||
case AddonManager.STATE_DOWNLOADING:
|
||||
this.showMessage("installDownloading");
|
||||
|
@ -566,6 +599,7 @@
|
|||
|
||||
}
|
||||
|
||||
this._installRemote.hidden = !showInstallRemote;
|
||||
this._restartInstall.hidden = !showRestartInstall;
|
||||
this._restartNeeded.hidden = !showRestartNeeded;
|
||||
this._undo.hidden = !showUndo;
|
||||
|
@ -587,6 +621,18 @@
|
|||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="installRemote">
|
||||
<body><![CDATA[
|
||||
if (this.mControl.getAttribute("remote") != "true")
|
||||
return;
|
||||
|
||||
delete this.mControl.mAddon;
|
||||
this.mControl.mInstall = this.mInstall;
|
||||
this.mControl.setAttribute("status", "installing");
|
||||
this.mInstall.install();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="restartInstall">
|
||||
<body><![CDATA[
|
||||
this.mInstall.install();
|
||||
|
@ -676,7 +722,7 @@
|
|||
</binding>
|
||||
|
||||
|
||||
<!-- Addon - generic - An normal installed addon item -->
|
||||
<!-- Addon - generic - A normal addon item -->
|
||||
<binding id="addon-generic"
|
||||
extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
|
||||
<content>
|
||||
|
@ -927,8 +973,8 @@
|
|||
<body><![CDATA[
|
||||
var bytes = this.getAttribute("size"); // this.mAddon.size;
|
||||
var formatted = gStrings.dl.GetStringFromName("doneSizeUnknown");
|
||||
if (bytes && bytes > 0) {
|
||||
let [size, unit] = DownloadUtils.convertByteUnits(bytes);
|
||||
if (bytes && !isNaN(bytes) && bytes > 0) {
|
||||
let [size, unit] = DownloadUtils.convertByteUnits(parseInt(bytes));
|
||||
formatted = gStrings.dl.GetStringFromName("doneSize");
|
||||
formatted = formatted.replace("#1", size).replace("#2", unit);
|
||||
}
|
||||
|
@ -975,7 +1021,11 @@
|
|||
|
||||
var pending = this.mAddon.pendingOperations;
|
||||
this.setAttribute("pending", pending != 0);
|
||||
this._showStatus(pending != 0 ? "progress" : "none");
|
||||
|
||||
var showProgress = pending != 0;
|
||||
if (this.getAttribute("remote") == "true")
|
||||
showProgress = showProgress || !!this.mAddon.install;
|
||||
this._showStatus(showProgress ? "progress" : "none");
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
</hbox>
|
||||
<textbox id="header-search" type="search" searchbutton="true"
|
||||
placeholder="&search.placeholder;"/>
|
||||
<image id="header-searching"/>
|
||||
</hbox>
|
||||
|
||||
<!-- view port -->
|
||||
|
@ -179,10 +180,11 @@
|
|||
<vbox>
|
||||
<hbox class="search-filter">
|
||||
<label value="&search.filter.label;"/>
|
||||
<checkbox id="search-filter-local" checked="true" disabled="true"
|
||||
<checkbox id="search-filter-local"
|
||||
persist="checked" checked="true"
|
||||
label="&search.filter.installed.label;"/>
|
||||
<checkbox id="search-filter-remote"
|
||||
checked="false" disabled="true"
|
||||
persist="checked" checked="true"
|
||||
label="&search.filter.available.label;"/>
|
||||
</hbox>
|
||||
<vbox id="search-list-empty" class="empty-list-notice"
|
||||
|
|
|
@ -49,14 +49,17 @@ _TEST_FILES = \
|
|||
head.js \
|
||||
browser_bug562890.js \
|
||||
browser_bug562899.js \
|
||||
browser_bug562992.js \
|
||||
browser_bug572561.js \
|
||||
browser_dragdrop.js \
|
||||
browser_searching.js \
|
||||
browser_searching.xml \
|
||||
browser_searching_empty.xml \
|
||||
browser_sorting.js \
|
||||
browser_updatessl.js \
|
||||
browser_updatessl.rdf \
|
||||
browser_installssl.js \
|
||||
redirect.sjs \
|
||||
browser_bug562992.js \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
|
9
toolkit/mozapps/extensions/test/browser/addons/browser_searching/bootstrap.js
поставляемый
Normal file
9
toolkit/mozapps/extensions/test/browser/addons/browser_searching/bootstrap.js
поставляемый
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function install(data, reason) {}
|
||||
function startup(data, reason) {}
|
||||
function shutdown(data, reason) {}
|
||||
function uninstall(data, reason) {}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>remote1@tests.mozilla.org</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>PASS - b - installed</em:name>
|
||||
<em:description>Test sumary - SEARCH SEARCH</em:description>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
|
@ -0,0 +1,592 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Tests that searching for add-ons works correctly
|
||||
|
||||
const PREF_GETADDONS_GETSEARCHRESULTS = "extensions.getAddons.search.url";
|
||||
const SEARCH_URL = TESTROOT + "browser_searching.xml";
|
||||
const NO_MATCH_URL = TESTROOT + "browser_searching_empty.xml";
|
||||
|
||||
const QUERY = "SEARCH";
|
||||
const NO_MATCH_QUERY = "NOMATCHQUERY";
|
||||
const REMOTE_TO_INSTALL = "remote1";
|
||||
const REMOTE_INSTALL_URL = TESTROOT + "addons/browser_searching.xpi";
|
||||
|
||||
var gManagerWindow;
|
||||
var gCategoryUtilities;
|
||||
var gProvider;
|
||||
var gServer;
|
||||
var gAddonInstalled = false;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gProvider = new MockProvider();
|
||||
|
||||
gProvider.createAddons([{
|
||||
id: "addon1@tests.mozilla.org",
|
||||
name: "PASS - f",
|
||||
description: "Test description - SEARCH",
|
||||
size: 3,
|
||||
version: "1.0",
|
||||
updateDate: new Date(2010, 4, 2, 0, 0, 1)
|
||||
}, {
|
||||
id: "fail-addon1@tests.mozilla.org",
|
||||
name: "FAIL",
|
||||
description: "Does not match query"
|
||||
}, {
|
||||
id: "addon2@tests.mozilla.org",
|
||||
name: "PASS - c",
|
||||
description: "Test description - reSEARCHing SEARCH SEARCH",
|
||||
size: 6,
|
||||
version: "2.0",
|
||||
updateDate: new Date(2010, 4, 2, 0, 0, 0)
|
||||
}]);
|
||||
|
||||
var installs = gProvider.createInstalls([{
|
||||
name: "PASS - a - SEARCHing",
|
||||
sourceURI: "http://example.com/install1.xpi"
|
||||
}, {
|
||||
name: "PASS - g - reSEARCHing SEARCH",
|
||||
sourceURI: "http://example.com/install2.xpi"
|
||||
}, {
|
||||
// Does not match query
|
||||
name: "FAIL",
|
||||
sourceURI: "http://example.com/fail-install1.xpi"
|
||||
}]);
|
||||
|
||||
installs.forEach(function(aInstall) { aInstall.install(); });
|
||||
|
||||
open_manager(null, function(aWindow) {
|
||||
gManagerWindow = aWindow;
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
|
||||
function end_test() {
|
||||
close_manager(gManagerWindow, function() {
|
||||
var installedAddon = get_addon_item(REMOTE_TO_INSTALL).mAddon;
|
||||
installedAddon.uninstall();
|
||||
|
||||
AddonManager.getAllInstalls(function(aInstallsList) {
|
||||
for (var i = 0; i < aInstallsList.length; i++) {
|
||||
var install = aInstallsList[i];
|
||||
var sourceURI = install.sourceURI.spec;
|
||||
if (sourceURI == REMOTE_INSTALL_URL ||
|
||||
sourceURI.match(/^http:\/\/example\.com\/(.+)\.xpi$/) != null)
|
||||
install.cancel();
|
||||
}
|
||||
|
||||
finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getAnonymousElementByAttribute(aElement, aName, aValue) {
|
||||
return gManagerWindow.document.getAnonymousElementByAttribute(aElement,
|
||||
aName,
|
||||
aValue);
|
||||
}
|
||||
|
||||
/*
|
||||
* Completes a search
|
||||
*
|
||||
* @param aQuery
|
||||
* The query to search for
|
||||
* @param aFinishImmediately
|
||||
* Boolean representing whether or not the search is expected to
|
||||
* finish immediately
|
||||
* @param aCallback
|
||||
* The callback to call when the search is done
|
||||
* @param aCategoryType
|
||||
* The expected selected category after the search is done.
|
||||
* Optional and defaults to "search"
|
||||
*/
|
||||
function search(aQuery, aFinishImmediately, aCallback, aCategoryType) {
|
||||
// Point search to the correct xml test file
|
||||
var url = (aQuery == NO_MATCH_QUERY) ? NO_MATCH_URL : SEARCH_URL;
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_GETSEARCHRESULTS, url);
|
||||
|
||||
aCategoryType = aCategoryType ? aCategoryType : "search";
|
||||
|
||||
var searchBox = gManagerWindow.document.getElementById("header-search");
|
||||
searchBox.value = aQuery;
|
||||
|
||||
EventUtils.synthesizeMouse(searchBox, 2, 2, { }, gManagerWindow);
|
||||
EventUtils.synthesizeKey("VK_RETURN", { }, gManagerWindow);
|
||||
|
||||
var finishImmediately = true;
|
||||
wait_for_view_load(gManagerWindow, function() {
|
||||
is(gCategoryUtilities.selectedCategory, aCategoryType, "Expected category view should be selected");
|
||||
is(gCategoryUtilities.isTypeVisible("search"), aCategoryType == "search",
|
||||
"Search category should only be visible if it is the current view");
|
||||
is(gManagerWindow.gHeader.isSearching, false, "Should no longer be searching");
|
||||
is(finishImmediately, aFinishImmediately, "Search should finish immediately only if expected");
|
||||
|
||||
aCallback();
|
||||
});
|
||||
|
||||
finishImmediately = false
|
||||
if (!aFinishImmediately)
|
||||
ok(gManagerWindow.gHeader.isSearching, "Should be searching");
|
||||
}
|
||||
|
||||
/*
|
||||
* Return results of a search
|
||||
*
|
||||
* @return Array of objects, each containing the name and item of a specific
|
||||
* result
|
||||
*/
|
||||
function get_actual_results() {
|
||||
var list = gManagerWindow.document.getElementById("search-list");
|
||||
var rows = list.getElementsByTagName("richlistitem");
|
||||
|
||||
var results = [];
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var item = rows[i];
|
||||
|
||||
// Only consider items that are currently showing
|
||||
var style = gManagerWindow.document.defaultView.getComputedStyle(item, "");
|
||||
if (style.display == "none" || style.visibility != "visible")
|
||||
continue;
|
||||
|
||||
if (item.mInstall) {
|
||||
var sourceURI = item.mInstall.sourceURI.spec;
|
||||
if (sourceURI == REMOTE_INSTALL_URL) {
|
||||
results.push({name: REMOTE_TO_INSTALL, item: item});
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = sourceURI.match(/^http:\/\/example\.com\/(.+)\.xpi$/);
|
||||
if (result != null) {
|
||||
is(item.mInstall.name.indexOf("PASS"), 0, "Install name should start with PASS");
|
||||
results.push({name: result[1], item: item});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.mAddon) {
|
||||
var result = item.mAddon.id.match(/^(.+)@tests\.mozilla\.org$/);
|
||||
if (result != null) {
|
||||
is(item.mAddon.name.indexOf("PASS"), 0, "Addon name should start with PASS");
|
||||
results.push({name: result[1], item: item});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns expected results when searching for QUERY with default ordering
|
||||
*
|
||||
* @param aSortBy
|
||||
* How the results are sorted (e.g. "name")
|
||||
* @param aLocalExpected
|
||||
* Boolean representing if local results are expected
|
||||
* @param aRemoteExpected
|
||||
* Boolean representing if remote results are expected
|
||||
* @return A pair: [array of results with an expected order,
|
||||
* array of results with unknown order]
|
||||
*/
|
||||
function get_expected_results(aSortBy, aLocalExpected, aRemoteExpected) {
|
||||
var expectedOrder = null, unknownOrder = null;
|
||||
switch (aSortBy) {
|
||||
case "relevancescore":
|
||||
expectedOrder = [ "remote4" , "addon2", "remote1" , "remote2",
|
||||
"install2", "addon1", "install1", "remote3" ];
|
||||
unknownOrder = [];
|
||||
break;
|
||||
case "name":
|
||||
// Defaults to ascending order
|
||||
expectedOrder = [ "install1", "remote1", "addon2" , "remote2",
|
||||
"remote3" , "addon1" , "install2", "remote4" ];
|
||||
unknownOrder = [];
|
||||
break;
|
||||
case "size":
|
||||
expectedOrder = [ "addon2" , "remote2", "remote4", "addon1",
|
||||
"remote1", "remote3" ];
|
||||
// Size data not available for installs
|
||||
unknownOrder = [ "install1", "install2" ];
|
||||
break;
|
||||
case "dateUpdated":
|
||||
expectedOrder = [ "addon1", "addon2" ];
|
||||
// Updated date not available for installs and remote add-ons
|
||||
unknownOrder = [ "install1", "install2", "remote1",
|
||||
"remote2" , "remote3" , "remote4" ];
|
||||
break;
|
||||
default:
|
||||
ok(false, "Should recognize sortBy when checking the order of items");
|
||||
}
|
||||
|
||||
// Only keep expected results
|
||||
function filterResults(aId) {
|
||||
// Include REMOTE_TO_INSTALL as a local add-on if it has been installed
|
||||
if (gAddonInstalled && aId == REMOTE_TO_INSTALL)
|
||||
return aLocalExpected;
|
||||
|
||||
if (aId.indexOf("addon") == 0 || aId.indexOf("install") == 0)
|
||||
return aLocalExpected;
|
||||
if (aId.indexOf("remote") == 0)
|
||||
return aRemoteExpected;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return [expectedOrder.filter(filterResults),
|
||||
unknownOrder.filter(filterResults)]
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the actual and expected results are the same
|
||||
*
|
||||
* @param aQuery
|
||||
* The search query used
|
||||
* @param aSortBy
|
||||
* How the results are sorted (e.g. "name")
|
||||
* @param aReverseOrder
|
||||
* Boolean representing if the results are in reverse default order
|
||||
* @param aFilterLocal
|
||||
* Boolean representing if local results should be filtered out or not
|
||||
* @param aFilterRemote
|
||||
* Boolean representing if remote results should be filtered out or not
|
||||
*/
|
||||
function check_results(aQuery, aSortBy, aReverseOrder, aFilterLocal, aFilterRemote) {
|
||||
var localFilterChecked = gManagerWindow.document.getElementById("search-filter-local").checked;
|
||||
var remoteFilterChecked = gManagerWindow.document.getElementById("search-filter-remote").checked;
|
||||
is(localFilterChecked, !aFilterLocal, "Local filter should be checked if showing local items");
|
||||
is(remoteFilterChecked, !aFilterRemote, "Remote filter should be checked if showing remote items");
|
||||
|
||||
// Get expected order assuming default order
|
||||
var expectedOrder = [], unknownOrder = [];
|
||||
if (aQuery == QUERY)
|
||||
[expectedOrder, unknownOrder] = get_expected_results(aSortBy, !aFilterLocal, !aFilterRemote);
|
||||
|
||||
// Get actual order of results
|
||||
var actualResults = get_actual_results();
|
||||
var actualOrder = [result.name for each(result in actualResults)];
|
||||
|
||||
// Reverse array of actual results if supposed to be in reverse order.
|
||||
// Reverse actualOrder instead of expectedOrder so can always check
|
||||
// expectedOrder before unknownOrder
|
||||
if (aReverseOrder)
|
||||
actualOrder.reverse();
|
||||
|
||||
// Check actual vs. expected list of results
|
||||
var totalExpectedResults = expectedOrder.length + unknownOrder.length;
|
||||
is(actualOrder.length, totalExpectedResults, "Should get correct number of results");
|
||||
|
||||
var i = 0;
|
||||
for (; i < expectedOrder.length; i++)
|
||||
is(actualOrder[i], expectedOrder[i], "Should have seen expected item");
|
||||
|
||||
// Items with data that is unknown can appear in any order among themselves,
|
||||
// so just check that these items exist
|
||||
for (; i < actualOrder.length; i++) {
|
||||
var unknownOrderIndex = unknownOrder.indexOf(actualOrder[i]);
|
||||
ok(unknownOrderIndex >= 0, "Should expect to see item with data that is unknown");
|
||||
unknownOrder[unknownOrderIndex] = null;
|
||||
}
|
||||
|
||||
// Check status of empty notice
|
||||
var emptyNotice = gManagerWindow.document.getElementById("search-list-empty");
|
||||
is(emptyNotice.hidden, totalExpectedResults > 0,
|
||||
"Empty notice should be hidden only if expecting shown items");
|
||||
}
|
||||
|
||||
/*
|
||||
* Check results of a search with different filterings
|
||||
*
|
||||
* @param aQuery
|
||||
* The search query used
|
||||
* @param aSortBy
|
||||
* How the results are sorted (e.g. "name")
|
||||
* @param aReverseOrder
|
||||
* Boolean representing if the results are in reverse default order
|
||||
*/
|
||||
function check_filtered_results(aQuery, aSortBy, aReverseOrder) {
|
||||
var localFilter = gManagerWindow.document.getElementById("search-filter-local");
|
||||
var remoteFilter = gManagerWindow.document.getElementById("search-filter-remote");
|
||||
|
||||
var list = gManagerWindow.document.getElementById("search-list");
|
||||
list.ensureElementIsVisible(localFilter);
|
||||
|
||||
// Check with no filtering
|
||||
check_results(aQuery, aSortBy, aReverseOrder, false, false);
|
||||
|
||||
// Check with filtering out local add-ons
|
||||
EventUtils.synthesizeMouse(localFilter, 2, 2, { }, gManagerWindow);
|
||||
check_results(aQuery, aSortBy, aReverseOrder, true, false);
|
||||
|
||||
// Check with filtering out both local and remote add-ons
|
||||
EventUtils.synthesizeMouse(remoteFilter, 2, 2, { }, gManagerWindow);
|
||||
check_results(aQuery, aSortBy, aReverseOrder, true, true);
|
||||
|
||||
// Check with filtering out remote add-ons
|
||||
EventUtils.synthesizeMouse(localFilter, 2, 2, { }, gManagerWindow);
|
||||
check_results(aQuery, aSortBy, aReverseOrder, false, true);
|
||||
|
||||
// Set back to no filtering
|
||||
EventUtils.synthesizeMouse(remoteFilter, 2, 2, { }, gManagerWindow);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get item for a specific add-on by name
|
||||
*
|
||||
* @param aName
|
||||
* The name of the add-on to search for
|
||||
* @return Row of add-on if found, null otherwise
|
||||
*/
|
||||
function get_addon_item(aName) {
|
||||
var id = aName + "@tests.mozilla.org";
|
||||
var list = gManagerWindow.document.getElementById("search-list");
|
||||
var rows = list.getElementsByTagName("richlistitem");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var row = rows[i];
|
||||
if (row.mAddon && row.mAddon.id == id)
|
||||
return row;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get item for a specific install by name
|
||||
*
|
||||
* @param aName
|
||||
* The name of the install to search for
|
||||
* @return Row of install if found, null otherwise
|
||||
*/
|
||||
function get_install_item(aName) {
|
||||
var sourceURI = "http://example.com/" + aName + ".xpi";
|
||||
var list = gManagerWindow.document.getElementById("search-list");
|
||||
var rows = list.getElementsByTagName("richlistitem");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var row = rows[i];
|
||||
if (row.mInstall && row.mInstall.sourceURI.spec == sourceURI)
|
||||
return row;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the install button for a specific item
|
||||
*
|
||||
* @param aItem
|
||||
* The item to get the install button for
|
||||
* @return The install button for aItem
|
||||
*/
|
||||
function get_install_button(aItem) {
|
||||
isnot(aItem, null, "Item should not be null when checking state of install button");
|
||||
var installStatus = getAnonymousElementByAttribute(aItem, "anonid", "install-status");
|
||||
return getAnonymousElementByAttribute(installStatus, "anonid", "install-remote");
|
||||
}
|
||||
|
||||
|
||||
// Tests that searching for the empty string does nothing when not in the search view
|
||||
add_test(function() {
|
||||
is(gCategoryUtilities.isTypeVisible("search"), false, "Search category should initially be hidden");
|
||||
|
||||
var selectedCategory = gCategoryUtilities.selectedCategory;
|
||||
isnot(selectedCategory, "search", "Selected type should not initially be the search view");
|
||||
search("", true, run_next_test, selectedCategory);
|
||||
});
|
||||
|
||||
// Tests that the results from a query are sorted by relevancescore in descending order.
|
||||
// Also test that double clicking non-install items goes to the detail view, and that
|
||||
// only remote items have install buttons showing
|
||||
add_test(function() {
|
||||
search(QUERY, false, function() {
|
||||
check_results(QUERY, "relevancescore", false);
|
||||
|
||||
var list = gManagerWindow.document.getElementById("search-list");
|
||||
var results = get_actual_results();
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var result = results[i];
|
||||
var installBtn = get_install_button(result.item);
|
||||
is(installBtn.hidden, result.name.indexOf("remote") != 0,
|
||||
"Install button should only be showing for remote items");
|
||||
}
|
||||
|
||||
var currentIndex = -1;
|
||||
function run_next_double_click_test() {
|
||||
currentIndex++;
|
||||
if (currentIndex >= results.length) {
|
||||
run_next_test();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = results[currentIndex];
|
||||
if (result.name.indexOf("install") == 0) {
|
||||
run_next_double_click_test();
|
||||
return;
|
||||
}
|
||||
|
||||
var item = result.item;
|
||||
list.ensureElementIsVisible(item);
|
||||
EventUtils.synthesizeMouse(item, 2, 2, { clickCount: 2 }, gManagerWindow);
|
||||
wait_for_view_load(gManagerWindow, function() {
|
||||
var name = gManagerWindow.document.getElementById("detail-name").value;
|
||||
is(name, item.mAddon.name, "Name in detail view should be correct");
|
||||
var version = gManagerWindow.document.getElementById("detail-version").value;
|
||||
is(version, item.mAddon.version, "Version in detail view should be correct");
|
||||
|
||||
var headerLink = gManagerWindow.document.getElementById("header-link");
|
||||
is(headerLink.hidden, false, "Header link should be showing in detail view");
|
||||
|
||||
EventUtils.synthesizeMouse(headerLink, 2, 2, { }, gManagerWindow);
|
||||
wait_for_view_load(gManagerWindow, run_next_double_click_test);
|
||||
});
|
||||
}
|
||||
|
||||
run_next_double_click_test();
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that the sorters and filters correctly manipulate the results
|
||||
add_test(function() {
|
||||
var sorters = gManagerWindow.document.getElementById("search-sorters");
|
||||
var originalHandler = sorters.handler;
|
||||
|
||||
var sorterNames = ["name", "size", "dateUpdated"];
|
||||
var buttonIds = ["btn-name", "btn-size", "btn-date"];
|
||||
var currentIndex = 0;
|
||||
var currentReversed = false;
|
||||
|
||||
function run_sort_test() {
|
||||
if (currentIndex >= sorterNames.length) {
|
||||
sorters.handler = originalHandler;
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Simulate clicking on a specific sorter
|
||||
var buttonId = buttonIds[currentIndex];
|
||||
var sorter = getAnonymousElementByAttribute(sorters, "anonid", buttonId);
|
||||
EventUtils.synthesizeMouse(sorter, 2, 2, { }, gManagerWindow);
|
||||
}
|
||||
|
||||
sorters.handler = {
|
||||
onSortChanged: function(aSortBy, aAscending) {
|
||||
if (originalHandler && "onSortChanged" in originalHandler)
|
||||
originalHandler.onSortChanged(aSortBy, aAscending);
|
||||
|
||||
check_filtered_results(QUERY, sorterNames[currentIndex], currentReversed);
|
||||
|
||||
if (currentReversed)
|
||||
currentIndex++;
|
||||
currentReversed = !currentReversed;
|
||||
|
||||
run_sort_test();
|
||||
}
|
||||
};
|
||||
|
||||
check_filtered_results(QUERY, "relevancescore", false);
|
||||
run_sort_test();
|
||||
});
|
||||
|
||||
// Tests that searching for the empty string does nothing when in search view
|
||||
add_test(function() {
|
||||
search("", true, function() {
|
||||
check_results(QUERY, "dateUpdated", true);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that clicking a different category hides the search query
|
||||
add_test(function() {
|
||||
gCategoryUtilities.openType("extension", function() {
|
||||
is(gCategoryUtilities.isTypeVisible("search"), false, "Search category should be hidden");
|
||||
is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that re-searching for query doesn't actually complete a new search,
|
||||
// and the last sort is still used
|
||||
add_test(function() {
|
||||
search(QUERY, true, function() {
|
||||
check_results(QUERY, "dateUpdated", true);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that getting zero results works correctly
|
||||
add_test(function() {
|
||||
search(NO_MATCH_QUERY, false, function() {
|
||||
check_filtered_results(NO_MATCH_QUERY, "relevancescore", false);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that installing a remote add-on works
|
||||
add_test(function() {
|
||||
var installBtn = null;
|
||||
|
||||
var listener = {
|
||||
onInstallEnded: function(aInstall, aAddon) {
|
||||
// Don't immediately consider the installed add-on as local because
|
||||
// if the user was filtering out local add-ons, the installed add-on
|
||||
// would vanish. Only consider add-on as local on new searches.
|
||||
|
||||
aInstall.removeListener(this);
|
||||
|
||||
is(installBtn.hidden, true, "Install button should be hidden after install ended");
|
||||
check_filtered_results(QUERY, "relevancescore", false);
|
||||
run_next_test();
|
||||
}
|
||||
}
|
||||
|
||||
search(QUERY, false, function() {
|
||||
var list = gManagerWindow.document.getElementById("search-list");
|
||||
var remoteItem = get_addon_item(REMOTE_TO_INSTALL);
|
||||
list.ensureElementIsVisible(remoteItem);
|
||||
|
||||
installBtn = get_install_button(remoteItem);
|
||||
is(installBtn.hidden, false, "Install button should be showing before install");
|
||||
remoteItem.mAddon.install.addListener(listener);
|
||||
EventUtils.synthesizeMouse(installBtn, 2, 2, { }, gManagerWindow);
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that re-searching for query results in correct results
|
||||
add_test(function() {
|
||||
// Select a different category
|
||||
gCategoryUtilities.openType("extension", function() {
|
||||
is(gCategoryUtilities.isTypeVisible("search"), false, "Search category should be hidden");
|
||||
is(gCategoryUtilities.selectedCategory, "extension", "View should have changed to extension");
|
||||
|
||||
var installBtn = get_install_button(get_addon_item(REMOTE_TO_INSTALL));
|
||||
is(installBtn.hidden, true, "Install button should be hidden for installed item");
|
||||
|
||||
search(QUERY, true, function() {
|
||||
check_filtered_results(QUERY, "relevancescore", false);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that restarting the manager doesn't change search results
|
||||
add_test(function() {
|
||||
restart_manager(gManagerWindow, null, function(aWindow) {
|
||||
gManagerWindow = aWindow;
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
|
||||
// Installed add-on is considered local on new search
|
||||
gAddonInstalled = true;
|
||||
|
||||
search(QUERY, false, function() {
|
||||
check_filtered_results(QUERY, "relevancescore", false);
|
||||
|
||||
var installBtn = get_install_button(get_addon_item(REMOTE_TO_INSTALL));
|
||||
is(installBtn.hidden, true, "Install button should be hidden for installed item");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<searchresults total_results="100">
|
||||
<addon>
|
||||
<name>FAIL</name>
|
||||
<type id='1'>Extension</type>
|
||||
<guid>addon1@tests.mozilla.org</guid>
|
||||
<version>1.0</version>
|
||||
<status id='4'>Public</status>
|
||||
<summary>Addon already installed - SEARCH</summary>
|
||||
<description>Test description</description>
|
||||
<compatible_applications>
|
||||
<application>
|
||||
<name>Firefox</name>
|
||||
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
|
||||
<min_version>0</min_version>
|
||||
<max_version>*</max_version>
|
||||
</application>
|
||||
</compatible_applications>
|
||||
<compatible_os>ALL</compatible_os>
|
||||
<install size="1">http://example.com/addon1.xpi</install>
|
||||
</addon>
|
||||
<addon>
|
||||
<name>FAIL</name>
|
||||
<type id='1'>Extension</type>
|
||||
<guid>install1@tests.mozilla.org</guid>
|
||||
<version>1.0</version>
|
||||
<status id='4'>Public</status>
|
||||
<summary>Install already exists - SEARCH</summary>
|
||||
<description>Test description</description>
|
||||
<compatible_applications>
|
||||
<application>
|
||||
<name>Firefox</name>
|
||||
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
|
||||
<min_version>0</min_version>
|
||||
<max_version>*</max_version>
|
||||
</application>
|
||||
</compatible_applications>
|
||||
<compatible_os>ALL</compatible_os>
|
||||
<install size="1">http://example.com/install1.xpi</install>
|
||||
</addon>
|
||||
<addon>
|
||||
<name>PASS - b</name>
|
||||
<type id='1'>Extension</type>
|
||||
<guid>remote1@tests.mozilla.org</guid>
|
||||
<version>3.0</version>
|
||||
<status id='4'>Public</status>
|
||||
<summary>Test summary - SEARCH SEARCH</summary>
|
||||
<description>Test description</description>
|
||||
<compatible_applications>
|
||||
<application>
|
||||
<name>Firefox</name>
|
||||
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
|
||||
<min_version>0</min_version>
|
||||
<max_version>*</max_version>
|
||||
</application>
|
||||
</compatible_applications>
|
||||
<compatible_os>ALL</compatible_os>
|
||||
<install size="2">http://example.com/browser/toolkit/mozapps/extensions/test/browser/addons/browser_searching.xpi</install>
|
||||
</addon>
|
||||
<addon>
|
||||
<name>PASS - d</name>
|
||||
<type id='1'>Extension</type>
|
||||
<guid>remote2@tests.mozilla.org</guid>
|
||||
<version>4.0</version>
|
||||
<status id='4'>Public</status>
|
||||
<summary>Test summary - SEARCHing SEARCH</summary>
|
||||
<description>Test description</description>
|
||||
<compatible_applications>
|
||||
<application>
|
||||
<name>Firefox</name>
|
||||
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
|
||||
<min_version>0</min_version>
|
||||
<max_version>*</max_version>
|
||||
</application>
|
||||
</compatible_applications>
|
||||
<compatible_os>ALL</compatible_os>
|
||||
<install size="5">http://example.com/remote2.xpi</install>
|
||||
</addon>
|
||||
<addon>
|
||||
<name>PASS - e</name>
|
||||
<type id='1'>Extension</type>
|
||||
<guid>remote3@tests.mozilla.org</guid>
|
||||
<version>5.0</version>
|
||||
<status id='4'>Public</status>
|
||||
<summary>Test summary - Does not match query</summary>
|
||||
<description>Test description</description>
|
||||
<compatible_applications>
|
||||
<application>
|
||||
<name>Firefox</name>
|
||||
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
|
||||
<min_version>0</min_version>
|
||||
<max_version>*</max_version>
|
||||
</application>
|
||||
</compatible_applications>
|
||||
<compatible_os>ALL</compatible_os>
|
||||
<install size="1">http://example.com/remote3.xpi</install>
|
||||
</addon>
|
||||
<addon>
|
||||
<name>PASS - h</name>
|
||||
<type id='1'>Extension</type>
|
||||
<guid>remote4@tests.mozilla.org</guid>
|
||||
<version>6.0</version>
|
||||
<status id='4'>Public</status>
|
||||
<summary>Test summary - SEARCHing SEARCH SEARCH</summary>
|
||||
<description>Test description</description>
|
||||
<compatible_applications>
|
||||
<application>
|
||||
<name>Firefox</name>
|
||||
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
|
||||
<min_version>0</min_version>
|
||||
<max_version>*</max_version>
|
||||
</application>
|
||||
</compatible_applications>
|
||||
<compatible_os>ALL</compatible_os>
|
||||
<install size="4">http://example.com/remote4.xpi</install>
|
||||
</addon>
|
||||
</searchresults>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<searchresults total_results="100" />
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
const RELATIVE_DIR = "browser/toolkit/mozapps/extensions/test/browser/";
|
||||
|
||||
|
@ -59,6 +60,18 @@ function wait_for_view_load(aManagerWindow, aCallback) {
|
|||
}, false);
|
||||
}
|
||||
|
||||
function wait_for_manager_load(aManagerWindow, aCallback) {
|
||||
if (!aManagerWindow.gIsInitializing) {
|
||||
aCallback(aManagerWindow);
|
||||
return;
|
||||
}
|
||||
|
||||
aManagerWindow.document.addEventListener("Initialized", function() {
|
||||
aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false);
|
||||
aCallback(aManagerWindow);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function open_manager(aView, aCallback) {
|
||||
function setup_manager(aManagerWindow) {
|
||||
if (aView)
|
||||
|
@ -67,15 +80,9 @@ function open_manager(aView, aCallback) {
|
|||
ok(aManagerWindow != null, "Should have an add-ons manager window");
|
||||
is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI");
|
||||
|
||||
if (!aManagerWindow.gIsInitializing) {
|
||||
wait_for_manager_load(aManagerWindow, function() {
|
||||
wait_for_view_load(aManagerWindow, aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
aManagerWindow.document.addEventListener("Initialized", function() {
|
||||
aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false);
|
||||
wait_for_view_load(aManagerWindow, aCallback);
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
if ("switchToTabHavingURI" in window) {
|
||||
|
@ -177,7 +184,7 @@ CategoryUtilities.prototype = {
|
|||
|
||||
openType: function(aCategoryType, aCallback) {
|
||||
this.open(this.get(aCategoryType), aCallback);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function CertOverrideListener(host, bits) {
|
||||
|
@ -336,6 +343,11 @@ MockProvider.prototype = {
|
|||
aInstallProperties.forEach(function(aInstallProp) {
|
||||
var install = new MockInstall();
|
||||
for (var prop in aInstallProp) {
|
||||
if (prop == "sourceURI") {
|
||||
install[prop] = NetUtil.newURI(aInstallProp[prop]);
|
||||
continue;
|
||||
}
|
||||
|
||||
install[prop] = aInstallProp[prop];
|
||||
}
|
||||
this.addInstall(install);
|
||||
|
@ -381,6 +393,8 @@ MockProvider.prototype = {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
aCallback(null);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -642,8 +656,13 @@ MockInstall.prototype = {
|
|||
}
|
||||
|
||||
// Adding addon to MockProvider to be implemented when needed
|
||||
this.addon = this._addonToInstall ||
|
||||
new MockAddon("", this.name, this.type);
|
||||
if (this._addonToInstall)
|
||||
this.addon = this._addonToInstall;
|
||||
else {
|
||||
this.addon = new MockAddon("", this.name, this.type);
|
||||
this.addon.pendingOperations = AddonManager.PENDING_INSTALL;
|
||||
}
|
||||
|
||||
this.state = AddonManager.STATE_INSTALLED;
|
||||
this.callListeners("onInstallEnded");
|
||||
break;
|
||||
|
|
|
@ -164,6 +164,14 @@
|
|||
-moz-margin-end: 6px;
|
||||
}
|
||||
|
||||
#header-searching:not([active]) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#header-searching {
|
||||
list-style-image: url("chrome://global/skin/icons/loading_16.png");
|
||||
}
|
||||
|
||||
.view-header {
|
||||
background: -moz-linear-gradient(top, #FFF, #E8E8E8 50%, #FFF);
|
||||
padding: 4px;
|
||||
|
@ -406,6 +414,15 @@
|
|||
font-size: 120%;
|
||||
}
|
||||
|
||||
#search-list > .addon {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
#search-list[local="true"] > .addon[remote="false"],
|
||||
#search-list[remote="true"] > .addon[remote="true"] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
/*** detail view ***/
|
||||
|
||||
|
|
|
@ -164,6 +164,14 @@
|
|||
-moz-margin-end: 6px;
|
||||
}
|
||||
|
||||
#header-searching:not([active]) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#header-searching {
|
||||
list-style-image: url("chrome://global/skin/icons/loading_16.png");
|
||||
}
|
||||
|
||||
.view-header {
|
||||
background: -moz-linear-gradient(top, #FFF, #E8E8E8 50%, #FFF);
|
||||
padding: 4px;
|
||||
|
@ -406,6 +414,15 @@
|
|||
font-size: 120%;
|
||||
}
|
||||
|
||||
#search-list > .addon {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
#search-list[local="true"] > .addon[remote="false"],
|
||||
#search-list[remote="true"] > .addon[remote="true"] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
/*** detail view ***/
|
||||
|
||||
|
|
|
@ -164,6 +164,14 @@
|
|||
-moz-margin-end: 6px;
|
||||
}
|
||||
|
||||
#header-searching:not([active]) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#header-searching {
|
||||
list-style-image: url("chrome://global/skin/icons/loading_16.png");
|
||||
}
|
||||
|
||||
.view-header {
|
||||
background: -moz-linear-gradient(top, #FFF, #E8E8E8 50%, #FFF);
|
||||
padding: 4px;
|
||||
|
@ -406,6 +414,15 @@
|
|||
font-size: 120%;
|
||||
}
|
||||
|
||||
#search-list > .addon {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
#search-list[local="true"] > .addon[remote="false"],
|
||||
#search-list[remote="true"] > .addon[remote="true"] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
/*** detail view ***/
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче