Bug 558287: Add support for searching add-ons on AMO via the search bar. r=dtownsend

This commit is contained in:
Ben Parr 2010-07-19 16:01:23 -07:00
Родитель 0b374032b3
Коммит 48b38e2c5e
17 изменённых файлов: 1124 добавлений и 110 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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 ***/