Bug 595848: Dynamically generate the list of categories. r=Unfocused

This commit is contained in:
Dave Townsend 2011-05-20 10:36:28 -07:00
Родитель c837809318
Коммит e91f600ac1
18 изменённых файлов: 932 добавлений и 87 удалений

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

@ -30,11 +30,6 @@
<!-- categories / views -->
<!ENTITY view.search.label "Search">
<!ENTITY view.discover.label "Get Add-ons">
<!ENTITY view.locales.label "Languages">
<!ENTITY view.searchengines.label "Search Engines">
<!ENTITY view.features.label "Extensions">
<!ENTITY view.appearance2.label "Appearance">
<!ENTITY view.plugins.label "Plugins">
<!ENTITY view.recentUpdates.label "Recent Updates">
<!ENTITY view.availableUpdates.label "Available Updates">

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

@ -104,3 +104,8 @@ cmd.purchaseAddon.accesskey=u
#LOCALIZATION NOTE (eulaHeader) %S is name of the add-on asking the user to agree to the EULA
eulaHeader=%S requires that you accept the following End User License Agreement before installation can proceed:
type.extension.name=Extensions
type.theme.name=Appearance
type.locale.name=Languages
type.plugin.name=Plugins

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

@ -47,6 +47,8 @@ const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion";
const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion";
const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
const VALID_TYPES_REGEXP = /^[\w\-]+$/;
Components.utils.import("resource://gre/modules/Services.jsm");
var EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
@ -211,6 +213,54 @@ AddonScreenshot.prototype = {
}
}
/**
* A type of add-on, used by the UI to determine how to display different types
* of add-ons.
*
* @param aId
* The add-on type ID
* @param aLocaleURI
* The URI of a localized properties file to get the displayable name
* for the type from
* @param aLocaleKey
* The key for the string in the properties file or the actual display
* name if aLocaleURI is null. Include %ID% to include the type ID in
* the key
* @param aViewType
* The optional type of view to use in the UI
* @param aUIPriority
* The priority is used by the UI to list the types in order. Lower
* values push the type higher in the list.
* @param aFlags
* An option set of flags that customize the display of the add-on in
* the UI.
*/
function AddonType(aId, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) {
if (!aId)
throw new Error("An AddonType must have an ID");
if (aViewType && aUIPriority === undefined)
throw new Error("An AddonType with a defined view must have a set UI priority");
if (!aLocaleKey)
throw new Error("An AddonType must have a displayable name");
this.id = aId;
this.uiPriority = aUIPriority;
this.viewType = aViewType;
this.flags = aFlags;
if (aLocaleURI) {
this.__defineGetter__("name", function() {
delete this.name;
let bundle = Services.strings.createBundle(aLocaleURI);
this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aId));
return this.name;
});
}
else {
this.name = aLocaleKey;
}
}
var gStarted = false;
/**
@ -220,7 +270,56 @@ var gStarted = false;
var AddonManagerInternal = {
installListeners: [],
addonListeners: [],
typeListeners: [],
providers: [],
types: {},
// A read-only wrapper around the types dictionary
typesProxy: Proxy.create({
getOwnPropertyDescriptor: function(aName) {
if (!(aName in AddonManagerInternal.types))
return undefined;
return {
value: AddonManagerInternal.types[aName].type,
writable: false,
configurable: false,
enumerable: true
}
},
getPropertyDescriptor: function(aName) {
return this.getOwnPropertyDescriptor(aName);
},
getOwnPropertyNames: function() {
return Object.keys(AddonManagerInternal.types);
},
getPropertyNames: function() {
return this.getOwnPropertyNames();
},
delete: function(aName) {
// Not allowed to delete properties
return false;
},
defineProperty: function(aName, aProperty) {
// Ignore attempts to define properties
},
fix: function() {
return undefined;
},
// Despite MDC's claims to the contrary, it is required that this trap
// be defined
enumerate: function() {
// All properties are enumerable
return this.getPropertyNames();
}
}),
/**
* Initializes the AddonManager, loading any known providers and initializing
@ -294,10 +393,37 @@ var AddonManagerInternal = {
*
* @param aProvider
* The provider to register
* @param aTypes
* An array of add-on types
*/
registerProvider: function AMI_registerProvider(aProvider) {
registerProvider: function AMI_registerProvider(aProvider, aTypes) {
this.providers.push(aProvider);
if (aTypes) {
aTypes.forEach(function(aType) {
if (!(aType.id in this.types)) {
if (!VALID_TYPES_REGEXP.test(aType.id)) {
WARN("Ignoring invalid type " + aType.id);
return;
}
this.types[aType.id] = {
type: aType,
providers: [aProvider]
};
this.typeListeners.forEach(function(aListener) {
safeCall(function() {
aListener.onTypeAdded(aType);
});
});
}
else {
this.types[aType.id].providers.push(aProvider);
}
}, this);
}
// If we're registering after startup call this provider's startup.
if (gStarted)
callProvider(aProvider, "startup");
@ -318,6 +444,20 @@ var AddonManagerInternal = {
pos++;
}
for (let type in this.types) {
this.types[type].providers = this.types[type].providers.filter(function(p) p != aProvider);
if (this.types[type].providers.length == 0) {
let oldType = this.types[type].type;
delete this.types[type];
this.typeListeners.forEach(function(aListener) {
safeCall(function() {
aListener.onTypeRemoved(oldType);
});
});
}
}
// If we're unregistering after startup call this provider's shutdown.
if (gStarted)
callProvider(aProvider, "shutdown");
@ -334,6 +474,7 @@ var AddonManagerInternal = {
this.installListeners.splice(0);
this.addonListeners.splice(0);
this.typeListeners.splice(0);
gStarted = false;
},
@ -869,6 +1010,25 @@ var AddonManagerInternal = {
}
},
addTypeListener: function AMI_addTypeListener(aListener) {
if (!this.typeListeners.some(function(i) { return i == aListener; }))
this.typeListeners.push(aListener);
},
removeTypeListener: function AMI_removeTypeListener(aListener) {
let pos = 0;
while (pos < this.typeListeners.length) {
if (this.typeListeners[pos] == aListener)
this.typeListeners.splice(pos, 1);
else
pos++;
}
},
get addonTypes() {
return this.typesProxy;
},
get autoUpdateDefault() {
try {
return Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT);
@ -888,8 +1048,8 @@ var AddonManagerPrivate = {
AddonManagerInternal.startup();
},
registerProvider: function AMP_registerProvider(aProvider) {
AddonManagerInternal.registerProvider(aProvider);
registerProvider: function AMP_registerProvider(aProvider, aTypes) {
AddonManagerInternal.registerProvider(aProvider, aTypes);
},
unregisterProvider: function AMP_unregisterProvider(aProvider) {
@ -923,7 +1083,9 @@ var AddonManagerPrivate = {
AddonAuthor: AddonAuthor,
AddonScreenshot: AddonScreenshot
AddonScreenshot: AddonScreenshot,
AddonType: AddonType
};
/**
@ -1036,6 +1198,11 @@ var AddonManager = {
// The combination of all scopes.
SCOPE_ALL: 15,
// 1-15 are different built-in views for the add-on type
VIEW_TYPE_LIST: "list",
TYPE_UI_HIDE_EMPTY: 16,
// Constants for Addon.applyBackgroundUpdates.
// Indicates that the Addon should not update automatically.
AUTOUPDATE_DISABLE: 0,
@ -1114,6 +1281,18 @@ var AddonManager = {
AddonManagerInternal.removeAddonListener(aListener);
},
addTypeListener: function AM_addTypeListener(aListener) {
AddonManagerInternal.addTypeListener(aListener);
},
removeTypeListener: function AM_removeTypeListener(aListener) {
AddonManagerInternal.removeTypeListener(aListener);
},
get addonTypes() {
return AddonManagerInternal.addonTypes;
},
get autoUpdateDefault() {
return AddonManagerInternal.autoUpdateDefault;
}

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

@ -49,6 +49,10 @@ const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
const ADDON_TYPE = "theme";
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const DEFAULT_MAX_USED_THEMES_COUNT = 30;
const MAX_PREVIEW_SECONDS = 30;
@ -803,4 +807,8 @@ function _persistProgressListener(successCallback) {
};
}
AddonManagerPrivate.registerProvider(LightweightThemeManager);
AddonManagerPrivate.registerProvider(LightweightThemeManager, [
new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 5000)
]);

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

@ -45,6 +45,9 @@ var EXPORTED_SYMBOLS = [];
Components.utils.import("resource://gre/modules/AddonManager.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
["LOG", "WARN", "ERROR"].forEach(function(aName) {
this.__defineGetter__(aName, function() {
Components.utils.import("resource://gre/modules/AddonLogging.jsm");
@ -342,4 +345,8 @@ PluginWrapper.prototype = {
}
};
AddonManagerPrivate.registerProvider(PluginProvider);
AddonManagerPrivate.registerProvider(PluginProvider, [
new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 6000)
]);

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

@ -78,6 +78,9 @@ const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons";
const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon.";
const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul";
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const DIR_EXTENSIONS = "extensions";
const DIR_STAGE = "staged";
@ -7655,4 +7658,15 @@ WinRegInstallLocation.prototype = {
};
#endif
AddonManagerPrivate.registerProvider(XPIProvider);
AddonManagerPrivate.registerProvider(XPIProvider, [
new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 4000),
new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 5000),
new AddonManagerPrivate.AddonType("locale", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 2000,
AddonManager.TYPE_UI_HIDE_EMPTY)
]);

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

@ -55,6 +55,7 @@ const PREF_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
const PREF_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled";
const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
@ -106,9 +107,9 @@ __defineGetter__("gIsInitializing", function() gPendingInitializations > 0);
function initialize() {
document.removeEventListener("load", initialize, true);
gViewController.initialize();
gCategories.initialize();
gHeader.initialize();
gViewController.initialize();
gEventManager.initialize();
Services.obs.addObserver(sendEMPong, "EM-ping", false);
Services.obs.notifyObservers(window, "EM-loaded", "");
@ -165,6 +166,7 @@ function loadView(aViewId) {
if (!gViewController.initialViewSelected) {
// The caller opened the window and immediately loaded the view so it
// should be the initial history entry
gViewController.loadInitialView(aViewId);
} else {
gViewController.loadView(aViewId);
@ -176,6 +178,12 @@ function loadView(aViewId) {
* back/forward controls to work within the manager
*/
var HTML5History = {
get index() {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.sessionHistory.index;
},
get canGoBack() {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@ -229,6 +237,10 @@ var FakeHistory = {
pos: 0,
states: [null],
get index() {
return this.pos;
},
get canGoBack() {
return this.pos > 0;
},
@ -493,6 +505,7 @@ var gViewController = {
viewObjects: {},
viewChangeCallback: null,
initialViewSelected: false,
lastHistoryIndex: -1,
initialize: function() {
this.viewPort = document.getElementById("view-port");
@ -535,7 +548,24 @@ var gViewController = {
},
updateState: function(state) {
this.loadViewInternal(state.view, state.previousView, state);
try {
this.loadViewInternal(state.view, state.previousView, state);
this.lastHistoryIndex = gHistory.index;
}
catch (e) {
// The attempt to load the view failed, try moving further along history
if (this.lastHistoryIndex > gHistory.index) {
if (gHistory.canGoBack)
gHistory.back();
else
gViewController.replaceView(VIEW_DEFAULT);
} else {
if (gHistory.canGoForward)
gHistory.forward();
else
gViewController.replaceView(VIEW_DEFAULT);
}
}
},
parseViewId: function(aViewId) {
@ -564,8 +594,10 @@ var gViewController = {
view: aViewId,
previousView: this.currentViewId
};
if (!isRefresh)
if (!isRefresh) {
gHistory.pushState(state);
this.lastHistoryIndex = gHistory.index;
}
this.loadViewInternal(aViewId, this.currentViewId, state);
},
@ -1416,12 +1448,17 @@ function doPendingUninstalls(aListBox) {
var gCategories = {
node: null,
_search: null,
_maybeHidden: null,
initialize: function() {
this.node = document.getElementById("categories");
this._search = this.get("addons://search/");
var types = AddonManager.addonTypes;
for (var type in types)
this.onTypeAdded(types[type]);
AddonManager.addTypeListener(this);
try {
this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY);
} catch (e) { }
@ -1451,20 +1488,88 @@ var gCategories = {
gViewController.loadView(viewId);
}
}, false);
},
this._maybeHidden = ["addons://list/locale", "addons://list/searchengine"];
gPendingInitializations += this._maybeHidden.length;
this._maybeHidden.forEach(function(aId) {
var type = gViewController.parseViewId(aId).param;
getAddonsAndInstalls(type, function(aAddonsList, aInstallsList) {
shutdown: function() {
AddonManager.removeTypeListener(this);
},
_insertCategory: function(aId, aName, aView, aPriority, aStartHidden) {
// If this category already exists then don't re-add it
if (document.getElementById("category-" + aId))
return;
var category = document.createElement("richlistitem");
category.setAttribute("id", "category-" + aId);
category.setAttribute("value", aView);
category.setAttribute("class", "category");
category.setAttribute("name", aName);
category.setAttribute("tooltiptext", aName);
category.setAttribute("priority", aPriority);
category.setAttribute("hidden", aStartHidden);
var node = this.node.firstChild;
while (node = node.nextSibling) {
var nodePriority = parseInt(node.getAttribute("priority"));
// If the new type's priority is higher than this one then this is the
// insertion point
if (aPriority < nodePriority)
break;
// If the new type's priority is lower than this one then this is isn't
// the insertion point
if (aPriority > nodePriority)
continue;
// If the priorities are equal and the new type's name is earlier
// alphabetically then this is the insertion point
if (String.localeCompare(aName, node.getAttribute("name")) < 0)
break;
}
this.node.insertBefore(category, node);
},
_removeCategory: function(aId) {
var category = document.getElementById("category-" + aId);
if (!category)
return;
// If this category is currently selected then switch to the default view
if (this.node.selectedItem == category)
gViewController.replaceView(VIEW_DEFAULT);
this.node.removeChild(category);
},
onTypeAdded: function(aType) {
// Ignore types that we don't have a view object for
if (!(aType.viewType in gViewController.viewObjects))
return;
var aViewId = "addons://" + aType.viewType + "/" + aType.id;
var startHidden = false;
if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) {
var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id);
try {
startHidden = Services.prefs.getBoolPref(prefName);
}
catch (e) {
// Default to hidden
startHidden = true;
}
var self = this;
gPendingInitializations++;
getAddonsAndInstalls(aType.id, function(aAddonsList, aInstallsList) {
var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0);
var item = self.get(aId);
var item = self.get(aViewId);
// Don't load view that is becoming hidden
if (hidden && aId == gViewController.currentViewId)
if (hidden && aViewId == gViewController.currentViewId)
gViewController.loadView(VIEW_DEFAULT);
item.hidden = hidden;
Services.prefs.setBoolPref(prefName, hidden);
if (aAddonsList.length > 0 || aInstallsList.length > 0) {
notifyInitialized();
@ -1489,8 +1594,9 @@ var gCategories = {
},
_maybeShowCategory: function(aAddon) {
if (type == aAddon.type) {
self.get(aId).hidden = false;
if (aType.id == aAddon.type) {
self.get(aViewId).hidden = false;
Services.prefs.setBoolPref(prefName, false);
gEventManager.unregisterInstallListener(this);
}
}
@ -1498,16 +1604,14 @@ var gCategories = {
notifyInitialized();
});
});
}
this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority,
startHidden);
},
shutdown: function() {
// Force persist of hidden state. See bug 15232
var self = this;
this._maybeHidden.forEach(function(aId) {
var item = self.get(aId);
item.setAttribute("hidden", !!item.hidden);
});
onTypeRemoved: function(aType) {
this._removeCategory(aType.id);
},
get selected() {
@ -1801,6 +1905,7 @@ var gDiscoverView = {
gHistory.replaceState(state);
else
gHistory.pushState(state);
gViewController.lastHistoryIndex = gHistory.index;
}
gViewController.updateCommands();
@ -2228,6 +2333,9 @@ var gListView = {
},
show: function(aType, aRequest) {
if (!(aType in AddonManager.addonTypes))
throw new Error("Attempting to show unknown type " + aType);
this._type = aType;
this.node.setAttribute("type", aType);
this.showEmptyNotice(false);

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

@ -211,43 +211,20 @@
<richlistbox id="categories">
<richlistitem id="category-search" value="addons://search/"
class="category"
name="&view.search.label;"
name="&view.search.label;" priority="0"
tooltiptext="&view.search.label;" disabled="true"/>
<richlistitem id="category-discover" value="addons://discover/"
class="category"
name="&view.discover.label;"
name="&view.discover.label;" priority="1000"
tooltiptext="&view.discover.label;"/>
<richlistitem id="category-languages" value="addons://list/locale"
class="category"
name="&view.locales.label;"
tooltiptext="&view.locales.label;"
hidden="true" persist="hidden"/>
<richlistitem id="category-searchengines"
value="addons://list/searchengine"
class="category"
name="&view.searchengines.label;"
tooltiptext="&view.searchengines.label;"
hidden="true" persist="hidden"/>
<richlistitem id="category-extensions" value="addons://list/extension"
class="category"
name="&view.features.label;"
tooltiptext="&view.features.label;"/>
<richlistitem id="category-themes" value="addons://list/theme"
class="category"
name="&view.appearance2.label;"
tooltiptext="&view.appearance2.label;"/>
<richlistitem id="category-plugins" value="addons://list/plugin"
class="category"
name="&view.plugins.label;"
tooltiptext="&view.plugins.label;"/>
<richlistitem id="category-availableUpdates" value="addons://updates/available"
class="category"
name="&view.availableUpdates.label;"
name="&view.availableUpdates.label;" priority="100000"
tooltiptext="&view.availableUpdates.label;"
disabled="true"/>
<richlistitem id="category-recentUpdates" value="addons://updates/recent"
class="category"
name="&view.recentUpdates.label;"
name="&view.recentUpdates.label;" priority="101000"
tooltiptext="&view.recentUpdates.label;" disabled="true"/>
</richlistbox>

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

@ -86,6 +86,7 @@ _MAIN_TEST_FILES = \
browser_updateid.js \
browser_purchase.js \
browser_openDialog.js \
browser_types.js \
$(NULL)
_TEST_FILES = \

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

@ -185,7 +185,7 @@ add_test(function() {
info("Part 1");
is_in_list(aManager, "addons://list/extension", false, false);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugin"), { }, aManager);
wait_for_view_load(aManager, function(aManager) {
info("Part 2");
@ -303,7 +303,7 @@ add_test(function() {
info("Part 1");
is_in_list(aManager, "addons://list/extension", false, false);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugin"), { }, aManager);
wait_for_view_load(aManager, function(aManager) {
info("Part 2");
@ -356,7 +356,7 @@ add_test(function() {
info("Part 1");
is_in_list(aManager, "addons://list/plugin", false, false);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-extensions"), { }, aManager);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-extension"), { }, aManager);
wait_for_view_load(aManager, function(aManager) {
info("Part 2");
@ -625,7 +625,7 @@ add_test(function() {
info("Part 1");
is_in_list(aManager, "addons://list/extension", false, false);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugin"), { }, aManager);
wait_for_view_load(aManager, function(aManager) {
info("Part 2");
@ -783,7 +783,7 @@ add_test(function() {
waitForLoad(aManager, function() {
is_in_discovery(aManager, SECOND_URL, true, false);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugin"), { }, aManager);
wait_for_view_load(aManager, function(aManager) {
is_in_list(aManager, "addons://list/plugin", true, false);
@ -831,7 +831,7 @@ add_test(function() {
waitForLoad(aManager, function() {
is_in_discovery(aManager, SECOND_URL, true, false);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugin"), { }, aManager);
wait_for_view_load(aManager, function(aManager) {
is_in_list(aManager, "addons://list/plugin", true, false);

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

@ -33,7 +33,12 @@ let gExtensionProperties = {
function test() {
waitForExplicitFinish();
gProvider = new MockProvider();
gProvider = new MockProvider(true, [{
id: "mock-addon",
name: "Mock Add-ons",
uiPriority: 4500,
flags: AddonManager.TYPE_UI_VIEW_LIST
}]);
open_manager(VIEW_ID, function(aWindow) {
gManagerWindow = aWindow;

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

@ -20,7 +20,7 @@ add_test(function() {
is_element_hidden(button, "Plugin Check message button should be hidden");
info("Changing view to plugins")
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugins"), { }, aManager);
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("category-plugin"), { }, aManager);
wait_for_view_load(aManager, function(aManager) {
var button = aManager.document.querySelector("#list-view hbox.global-info-plugincheck button.button-link");

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

@ -0,0 +1,473 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Tests that registering new types works
var gManagerWindow;
var gCategoryUtilities;
var gProvider = {
};
var gTypes = [
new AddonManagerPrivate.AddonType("type1", null, "Type 1",
AddonManager.VIEW_TYPE_LIST, 4500),
new AddonManagerPrivate.AddonType("missing1", null, "Missing 1"),
new AddonManagerPrivate.AddonType("type2", null, "Type 1",
AddonManager.VIEW_TYPE_LIST, 5100,
AddonManager.TYPE_UI_HIDE_EMPTY),
{
id: "type3",
name: "Type 3",
uiPriority: 5200,
viewType: AddonManager.VIEW_TYPE_LIST
}
];
function go_back(aManager) {
if (gUseInContentUI) {
gBrowser.goBack();
} else {
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("back-btn"),
{ }, aManager);
}
}
function go_forward(aManager) {
if (gUseInContentUI) {
gBrowser.goForward();
} else {
EventUtils.synthesizeMouseAtCenter(aManager.document.getElementById("forward-btn"),
{ }, aManager);
}
}
function check_state(aManager, canGoBack, canGoForward) {
var doc = aManager.document;
if (gUseInContentUI) {
is(gBrowser.canGoBack, canGoBack, "canGoBack should be correct");
is(gBrowser.canGoForward, canGoForward, "canGoForward should be correct");
}
if (!is_hidden(doc.getElementById("back-btn"))) {
is(!doc.getElementById("back-btn").disabled, canGoBack, "Back button should have the right state");
is(!doc.getElementById("forward-btn").disabled, canGoForward, "Forward button should have the right state");
}
}
function test() {
waitForExplicitFinish();
run_next_test();
}
function end_test() {
finish();
}
// Add a new type, open the manager and make sure it is in the right place
add_test(function() {
AddonManagerPrivate.registerProvider(gProvider, gTypes);
open_manager(null, function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.get("type2"), "Type 2 should be present");
ok(!gCategoryUtilities.get("missing1", true), "Missing 1 should be absent");
is(gCategoryUtilities.get("type1").previousSibling.getAttribute("value"),
"addons://list/extension", "Type 1 should be in the right place");
is(gCategoryUtilities.get("type2").previousSibling.getAttribute("value"),
"addons://list/theme", "Type 2 should be in the right place");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
ok(!gCategoryUtilities.isTypeVisible("type2"), "Type 2 should be hidden");
run_next_test();
});
});
// Select the type, close the manager and remove it then open the manager and
// check we're back to the default view
add_test(function() {
gCategoryUtilities.openType("type1", function() {
close_manager(gManagerWindow, function() {
AddonManagerPrivate.unregisterProvider(gProvider);
open_manager(null, function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should be absent");
ok(!gCategoryUtilities.get("type2", true), "Type 2 should be absent");
ok(!gCategoryUtilities.get("missing1", true), "Missing 1 should be absent");
is(gCategoryUtilities.selectedCategory, "discover", "Should be back to the default view");
close_manager(gManagerWindow, run_next_test);
});
});
});
});
// Add a type while the manager is still open and check it appears
add_test(function() {
open_manager("addons://list/extension", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should be absent");
ok(!gCategoryUtilities.get("type2", true), "Type 2 should be absent");
ok(!gCategoryUtilities.get("missing1", true), "Missing 1 should be absent");
AddonManagerPrivate.registerProvider(gProvider, gTypes);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.get("type2"), "Type 2 should be present");
ok(!gCategoryUtilities.get("missing1", true), "Missing 1 should be absent");
is(gCategoryUtilities.get("type1").previousSibling.getAttribute("value"),
"addons://list/extension", "Type 1 should be in the right place");
is(gCategoryUtilities.get("type2").previousSibling.getAttribute("value"),
"addons://list/theme", "Type 2 should be in the right place");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
ok(!gCategoryUtilities.isTypeVisible("type2"), "Type 2 should be hidden");
run_next_test();
});
});
// Remove the type while it is beng viewed and check it is replaced with the
// default view
add_test(function() {
gCategoryUtilities.openType("type1", function() {
gCategoryUtilities.openType("plugin", function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "type1", "Should be showing the custom view");
check_state(gManagerWindow, true, true);
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should be absent");
ok(!gCategoryUtilities.get("type2", true), "Type 2 should be absent");
ok(!gCategoryUtilities.get("missing1", true), "Missing 1 should be absent");
is(gCategoryUtilities.selectedCategory, "discover", "Should be back to the default view");
check_state(gManagerWindow, true, true);
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "extension", "Should be showing the extension view");
check_state(gManagerWindow, false, true);
go_forward(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "discover", "Should be back to the default view");
check_state(gManagerWindow, true, true);
go_forward(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "plugin", "Should be back to the plugins view");
check_state(gManagerWindow, true, false);
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "discover", "Should be back to the default view");
check_state(gManagerWindow, true, true);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
});
});
});
// Test that when going back to a now missing category we skip it
add_test(function() {
open_manager("addons://list/extension", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
AddonManagerPrivate.registerProvider(gProvider, gTypes);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("type1", function() {
gCategoryUtilities.openType("plugin", function() {
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "extension", "Should be back to the first view");
check_state(gManagerWindow, false, true);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
// Test that when going forward to a now missing category we skip it
add_test(function() {
open_manager("addons://list/extension", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
AddonManagerPrivate.registerProvider(gProvider, gTypes);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("type1", function() {
gCategoryUtilities.openType("plugin", function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "extension", "Should be back to the extension view");
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_forward(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "plugin", "Should be back to the plugin view");
check_state(gManagerWindow, true, false);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
});
});
// Test that when going back to a now missing category and we can't go back any
// any further then we just display the default view
add_test(function() {
AddonManagerPrivate.registerProvider(gProvider, gTypes);
open_manager("addons://list/type1", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
is(gCategoryUtilities.selectedCategory, "type1", "Should be at the custom view");
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("extension", function() {
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "discover", "Should be at the default view");
check_state(gManagerWindow, false, true);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
// Test that when going forward to a now missing category and we can't go
// forward any further then we just display the default view
add_test(function() {
AddonManagerPrivate.registerProvider(gProvider, gTypes);
open_manager("addons://list/extension", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("type1", function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "extension", "Should be at the extension view");
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_forward(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "discover", "Should be at the default view");
check_state(gManagerWindow, true, false);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
// Test that when going back we skip multiple missing categories
add_test(function() {
open_manager("addons://list/extension", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
AddonManagerPrivate.registerProvider(gProvider, gTypes);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("type1", function() {
gCategoryUtilities.openType("type3", function() {
gCategoryUtilities.openType("plugin", function() {
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "extension", "Should be back to the first view");
check_state(gManagerWindow, false, true);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
});
// Test that when going forward we skip multiple missing categories
add_test(function() {
open_manager("addons://list/extension", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
AddonManagerPrivate.registerProvider(gProvider, gTypes);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("type1", function() {
gCategoryUtilities.openType("type3", function() {
gCategoryUtilities.openType("plugin", function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "extension", "Should be back to the extension view");
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_forward(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "plugin", "Should be back to the plugin view");
check_state(gManagerWindow, true, false);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
});
});
});
});
// Test that when going back we skip all missing categories and when we can't go
// back any any further then we just display the default view
add_test(function() {
AddonManagerPrivate.registerProvider(gProvider, gTypes);
open_manager("addons://list/type1", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
is(gCategoryUtilities.selectedCategory, "type1", "Should be at the custom view");
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("type3", function() {
gCategoryUtilities.openType("extension", function() {
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "discover", "Should be at the default view");
check_state(gManagerWindow, false, true);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
// Test that when going forward we skip all missing categories and when we can't
// go back any any further then we just display the default view
add_test(function() {
AddonManagerPrivate.registerProvider(gProvider, gTypes);
open_manager("addons://list/extension", function(aWindow) {
gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
ok(gCategoryUtilities.get("type1"), "Type 1 should be present");
ok(gCategoryUtilities.isTypeVisible("type1"), "Type 1 should be visible");
gCategoryUtilities.openType("type1", function() {
gCategoryUtilities.openType("type3", function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
go_back(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "extension", "Should be at the extension view");
AddonManagerPrivate.unregisterProvider(gProvider);
ok(!gCategoryUtilities.get("type1", true), "Type 1 should not be present");
go_forward(gManagerWindow);
wait_for_view_load(gManagerWindow, function() {
is(gCategoryUtilities.selectedCategory, "discover", "Should be at the default view");
check_state(gManagerWindow, true, false);
close_manager(gManagerWindow, run_next_test);
});
});
});
});
});
});
});

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

@ -382,7 +382,7 @@ CategoryUtilities.prototype = {
return (view.type == "list") ? view.param : view.type;
},
get: function(aCategoryType) {
get: function(aCategoryType, aAllowMissing) {
isnot(this.window, null, "Should not get category when manager window is not loaded");
var categories = this.window.document.getElementById("categories");
@ -396,7 +396,8 @@ CategoryUtilities.prototype = {
if (items.length)
return items[0];
ok(false, "Should have found a category with type " + aCategoryType);
if (!aAllowMissing)
ok(false, "Should have found a category with type " + aCategoryType);
return null;
},
@ -480,11 +481,17 @@ function addCertOverride(host, bits) {
/***** Mock Provider *****/
function MockProvider(aUseAsyncCallbacks) {
function MockProvider(aUseAsyncCallbacks, aTypes) {
this.addons = [];
this.installs = [];
this.callbackTimers = [];
this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks;
this.types = (aTypes === undefined) ? [{
id: "extension",
name: "Extensions",
uiPriority: 4000,
flags: AddonManager.TYPE_UI_VIEW_LIST
}] : aTypes;
var self = this;
registerCleanupFunction(function() {
@ -502,6 +509,7 @@ MockProvider.prototype = {
apiDelay: 10,
callbackTimers: null,
useAsyncCallbacks: null,
types: null,
/***** Utility functions *****/
@ -509,7 +517,7 @@ MockProvider.prototype = {
* Register this provider with the AddonManager
*/
register: function MP_register() {
AddonManagerPrivate.registerProvider(this);
AddonManagerPrivate.registerProvider(this, this.types);
},
/**

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

@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// This verifies that custom types can be defined and undefined
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
function run_test() {
startupManager();
do_check_false("test" in AddonManager.addonTypes);
let types = AddonManager.addonTypes;
// The dumbest provider possible
var provider = {
};
var expectedAdd = "test";
var expectedRemove = null;
AddonManager.addTypeListener({
onTypeAdded: function(aType) {
do_check_eq(aType.id, expectedAdd);
expectedAdd = null;
},
onTypeRemoved: function(aType) {
do_check_eq(aType.id, expectedRemove);
expectedRemove = null;
}
});
AddonManagerPrivate.registerProvider(provider, [{
id: "test",
name: "Test",
uiPriority: 1
}, {
id: "t$e%st",
name: "Test",
uiPriority: 1
}]);
do_check_eq(expectedAdd, null);
do_check_true("test" in types);
do_check_eq(types["test"].name, "Test");
do_check_false("t$e%st" in types);
delete types["test"];
do_check_true("test" in types);
types["foo"] = "bar";
do_check_false("foo" in types);
expectedRemove = "test";
AddonManagerPrivate.unregisterProvider(provider);
do_check_eq(expectedRemove, null);
do_check_false("test" in AddonManager.addonTypes);
// The cached reference to addonTypes is live
do_check_false("test" in types);
}

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

@ -256,19 +256,19 @@
#category-discover > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-discover.png");
}
#category-languages > .category-icon {
#category-locale > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-languages.png");
}
#category-searchengines > .category-icon {
#category-searchengine > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-searchengines.png");
}
#category-extensions > .category-icon {
#category-extension > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-extensions.png");
}
#category-themes > .category-icon {
#category-theme > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-themes.png");
}
#category-plugins > .category-icon {
#category-plugin > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-plugins.png");
}
#category-availableUpdates > .category-icon {

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

@ -288,19 +288,19 @@
#category-discover > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-discover.png");
}
#category-languages > .category-icon {
#category-locale > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-languages.png");
}
#category-searchengines > .category-icon {
#category-searchengine > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-searchengines.png");
}
#category-extensions > .category-icon {
#category-extension > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-extensions.png");
}
#category-themes > .category-icon {
#category-theme > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-themes.png");
}
#category-plugins > .category-icon {
#category-plugin > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-plugins.png");
}
#category-availableUpdates > .category-icon {

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

@ -346,19 +346,19 @@
#category-discover > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-discover.png");
}
#category-languages > .category-icon {
#category-locale > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-languages.png");
}
#category-searchengines > .category-icon {
#category-searchengine > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-searchengines.png");
}
#category-extensions > .category-icon {
#category-extension > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-extensions.png");
}
#category-themes > .category-icon {
#category-theme > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-themes.png");
}
#category-plugins > .category-icon {
#category-plugin > .category-icon {
list-style-image: url("chrome://mozapps/skin/extensions/category-plugins.png");
}
#category-availableUpdates > .category-icon {