Bug 653637 - Provide a simple way for addons to have preferences UI - Part 2; r=dtownsend ui-r=jboriss

This commit is contained in:
Geoff Lankow 2011-06-01 22:35:24 +12:00
Родитель db9550f627
Коммит 718a9be9dd
7 изменённых файлов: 249 добавлений и 39 удалений

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

@ -1212,6 +1212,12 @@ var AddonManager = {
// Indicates that the Addon should update automatically.
AUTOUPDATE_ENABLE: 2,
// Constants for how Addon options should be shown.
// Options will be opened in a new window
OPTIONS_TYPE_DIALOG: 1,
// Options will be displayed within the AM detail view
OPTIONS_TYPE_INLINE: 2,
getInstallForURL: function AM_getInstallForURL(aUrl, aCallback, aMimetype,
aHash, aName, aIconURL,
aVersion, aLoadGroup) {

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

@ -118,7 +118,7 @@ const TOOLKIT_ID = "toolkit@mozilla.org";
const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
const DB_SCHEMA = 4;
const DB_SCHEMA = 5;
const REQ_VERSION = 2;
#ifdef MOZ_COMPATABILITY_NIGHTLY
@ -131,8 +131,8 @@ const PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
// Properties that exist in the install manifest
const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL",
"updateKey", "optionsURL", "aboutURL", "iconURL",
"icon64URL"];
"updateKey", "optionsURL", "optionsType", "aboutURL",
"iconURL", "icon64URL"];
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
@ -700,11 +700,17 @@ function loadManifestFromRDF(aUri, aStream) {
// Only read the bootstrapped property for extensions
if (addon.type == "extension") {
addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";
if (addon.optionsType &&
addon.optionsType != AddonManager.OPTIONS_TYPE_DIALOG &&
addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE) {
throw new Error("Install manifest specifies unknown type: " + addon.optionsType);
}
}
else {
// Only extensions are allowed to provide an optionsURL or aboutURL. For
// Only extensions are allowed to provide an optionsURL, optionsType or aboutURL. For
// all other types they are silently ignored
addon.optionsURL = null;
addon.optionsType = null;
addon.aboutURL = null;
if (addon.type == "theme") {
@ -3675,11 +3681,11 @@ var XPIProvider = {
};
const FIELDS_ADDON = "internal_id, id, location, version, type, internalName, " +
"updateURL, updateKey, optionsURL, aboutURL, iconURL, " +
"icon64URL, defaultLocale, visible, active, userDisabled, " +
"appDisabled, pendingUninstall, descriptor, installDate, " +
"updateDate, applyBackgroundUpdates, bootstrap, skinnable, " +
"size, sourceURI, releaseNotesURI, softDisabled";
"updateURL, updateKey, optionsURL, optionsType, aboutURL, " +
"iconURL, icon64URL, defaultLocale, visible, active, " +
"userDisabled, appDisabled, pendingUninstall, descriptor, " +
"installDate, updateDate, applyBackgroundUpdates, bootstrap, " +
"skinnable, size, sourceURI, releaseNotesURI, softDisabled";
/**
* A helper function to log an SQL error.
@ -3819,8 +3825,8 @@ var XPIDatabase = {
addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :location, " +
":version, :type, :internalName, :updateURL, " +
":updateKey, :optionsURL, :aboutURL, :iconURL, " +
":icon64URL, :locale, :visible, :active, " +
":updateKey, :optionsURL, :optionsType, :aboutURL, " +
":iconURL, :icon64URL, :locale, :visible, :active, " +
":userDisabled, :appDisabled, :pendingUninstall, " +
":descriptor, :installDate, :updateDate, " +
":applyBackgroundUpdates, :bootstrap, :skinnable, " +
@ -4328,9 +4334,9 @@ var XPIDatabase = {
"internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"id TEXT, location TEXT, version TEXT, " +
"type TEXT, internalName TEXT, updateURL TEXT, " +
"updateKey TEXT, optionsURL TEXT, aboutURL TEXT, " +
"iconURL TEXT, icon64URL TEXT, " +
"defaultLocale INTEGER, " +
"updateKey TEXT, optionsURL TEXT, " +
"optionsType TEXT, aboutURL TEXT, iconURL TEXT, " +
"icon64URL TEXT, defaultLocale INTEGER, " +
"visible INTEGER, active INTEGER, " +
"userDisabled INTEGER, appDisabled INTEGER, " +
"pendingUninstall INTEGER, descriptor TEXT, " +
@ -6806,11 +6812,9 @@ function AddonWrapper(aAddon) {
});
}, this);
["optionsURL", "aboutURL"].forEach(function(aProp) {
this.__defineGetter__(aProp, function() {
return this.isActive ? aAddon[aProp] : null;
});
}, this);
this.__defineGetter__("aboutURL", function() {
return this.isActive ? aAddon["aboutURL"] : null;
});
["installDate", "updateDate"].forEach(function(aProp) {
this.__defineGetter__(aProp, function() new Date(aAddon[aProp]));
@ -6825,15 +6829,24 @@ function AddonWrapper(aAddon) {
});
}, this);
// Maps iconURL and icon64URL to the properties of the same name or icon.png
// and icon64.png in the add-on's files.
["icon", "icon64"].forEach(function(aProp) {
// Maps iconURL, icon64URL and optionsURL to the properties of the same name
// or icon.png, icon64.png and options.xul in the add-on's files.
["icon", "icon64", "options"].forEach(function(aProp) {
this.__defineGetter__(aProp + "URL", function() {
if (this.isActive && aAddon[aProp + "URL"])
return aAddon[aProp + "URL"];
if (this.hasResource(aProp + ".png"))
return this.getResourceURI(aProp + ".png").spec;
switch (aProp) {
case "icon":
case "icon64":
if (this.hasResource(aProp + ".png"))
return this.getResourceURI(aProp + ".png").spec;
break;
case "options":
if (this.isActive && this.hasResource(aProp + ".xul"))
return this.getResourceURI(aProp + ".xul").spec;
break;
}
if (aAddon._repositoryAddon)
return aAddon._repositoryAddon[aProp + "URL"];
@ -6842,6 +6855,22 @@ function AddonWrapper(aAddon) {
}, this);
}, this);
this.__defineGetter__("optionsType", function() {
if (!this.isActive)
return null;
if (aAddon.optionsType)
return aAddon.optionsType;
if (this.hasResource("options.xul"))
return AddonManager.OPTIONS_TYPE_INLINE;
if (this.optionsURL)
return AddonManager.OPTIONS_TYPE_DIALOG;
return null;
}, this);
PROP_LOCALE_SINGLE.forEach(function(aProp) {
this.__defineGetter__(aProp, function() {
// Override XPI creator if repository creator is defined

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

@ -924,11 +924,20 @@ var gViewController = {
cmd_showItemPreferences: {
isEnabled: function(aAddon) {
if (!aAddon)
if (!aAddon || !aAddon.isActive || !aAddon.optionsURL)
return false;
return aAddon.isActive && !!aAddon.optionsURL;
if (gViewController.currentViewObj == gDetailView &&
aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
return false;
}
return true;
},
doCommand: function(aAddon) {
if (gViewController.currentViewObj == gListView &&
aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
gViewController.commands.cmd_showItemDetails.doCommand(aAddon);
return;
}
var optionsURL = aAddon.optionsURL;
var windows = Services.wm.getEnumerator(null);
while (windows.hasMoreElements()) {
@ -2654,7 +2663,8 @@ var gDetailView = {
document.getElementById("detail-findUpdates-btn").hidden = false;
}
document.getElementById("detail-prefs-btn").hidden = !aIsRemote && !aAddon.optionsURL;
document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
!gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);
var gridRows = document.querySelectorAll("#detail-grid rows row");
for (var i = 0, first = true; i < gridRows.length; ++i) {
@ -2666,6 +2676,8 @@ var gDetailView = {
}
}
this.fillSettingsRows();
this.updateState();
gViewController.updateCommands();
@ -2797,6 +2809,71 @@ var gDetailView = {
this.node.removeAttribute("loading-extended");
},
emptySettingsRows: function () {
var lastRow = document.getElementById("detail-downloads");
var rows = lastRow.parentNode;
while (lastRow.nextSibling)
rows.removeChild(rows.lastChild);
},
fillSettingsRows: function () {
this.emptySettingsRows();
if (this._addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE)
return;
var rows = document.getElementById("detail-downloads").parentNode;
var xhr = new XMLHttpRequest();
xhr.open("GET", this._addon.optionsURL, false);
xhr.send();
var xml = xhr.responseXML;
var settings = xml.querySelectorAll(":root > setting");
// This horrible piece of code fixes two problems. 1) The menulist binding doesn't apply
// correctly when it's moved from one document to another (bug 659163), which is solved
// by manually cloning the menulist. 2) Labels and controls aligned to the top of a row
// looks really bad, so the description is put on a new row to preserve alignment.
for (var i = 0; i < settings.length; i++) {
var setting = settings[i];
if (i == 0)
setting.setAttribute("first-row", true);
// remove menulist controls for replacement later
var control = setting.firstElementChild;
if (setting.getAttribute("type") == "control" && control && control.localName == "menulist") {
setting.removeChild(control);
var consoleMessage = Cc["@mozilla.org/scripterror;1"].
createInstance(Ci.nsIScriptError);
consoleMessage.init("Menulist is not available in the addons-manager yet, due to bug 659163",
this._addon.optionsURL, null, null, 0, Ci.nsIScriptError.warningFlag, null);
Services.console.logMessage(consoleMessage);
continue;
}
// remove setting description, for replacement later
var desc = setting.textContent.trim();
if (desc)
setting.textContent = "";
if (setting.hasAttribute("desc")) {
desc = setting.getAttribute("desc");
setting.removeAttribute("desc");
}
rows.appendChild(setting);
// add a new row containing the description
if (desc) {
var row = document.createElement("row");
var label = document.createElement("label");
label.className = "preferences-description";
label.textContent = desc;
row.appendChild(label);
rows.appendChild(row);
}
}
},
getSelectedAddon: function() {
return this._addon;
},
@ -2807,6 +2884,7 @@ var gDetailView = {
onEnabled: function() {
this.updateState();
this.fillSettingsRows();
},
onDisabling: function() {
@ -2815,6 +2893,7 @@ var gDetailView = {
onDisabled: function() {
this.updateState();
this.emptySettingsRows();
},
onUninstalling: function() {

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

@ -270,7 +270,7 @@
</xul:label>
</xul:vbox>
<xul:hbox anonid="input-container" class="setting-input">
<xul:textbox type="number" anonid="input" xbl:inherits="disabled,emptytext,min,max,increment,hidespinbuttons,wraparound" oninput="inputChanged();" oncommand="inputChanged();"/>
<xul:textbox type="number" anonid="input" xbl:inherits="disabled,emptytext,min,max,increment,hidespinbuttons,wraparound" oninput="inputChanged();" onchange="inputChanged();"/>
</xul:hbox>
</content>

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

@ -717,15 +717,22 @@
margin-bottom: 2em;
}
#detail-grid > columns > column:first-child {
max-width: 25em;
}
.detail-row[first-row="true"],
.detail-row-complex[first-row="true"] {
.detail-row-complex[first-row="true"],
setting[first-row="true"] {
border-top: none;
}
.detail-row,
.detail-row-complex {
.detail-row-complex,
setting {
border-top: 1px solid ThreeDShadow;
-moz-box-align: center;
min-height: 33px;
}
.detail-row-value {
@ -741,6 +748,30 @@
rgba(135, 135, 135, 0));
}
setting[first-row="true"] {
margin-top: 2em;
}
setting {
display: -moz-grid-line;
}
.preferences-description {
font-size: 90.9%;
color: graytext;
margin-top: -2px;
-moz-margin-start: 2em;
}
setting[type="string"] > .setting-input > textbox {
-moz-box-flex: 1;
}
menulist { /* Fixes some styling inconsistencies */
font-size: 100%;
margin: 1px 5px 2px 5px;
}
/*** creator ***/

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

@ -894,16 +894,23 @@
margin-bottom: 2em;
}
#detail-grid > columns > column:first-child {
max-width: 25em;
}
.detail-row[first-row="true"],
.detail-row-complex[first-row="true"] {
.detail-row-complex[first-row="true"],
setting[first-row="true"] {
border-top: none;
}
.detail-row,
.detail-row-complex {
.detail-row-complex,
setting {
border-top: 2px solid;
-moz-border-top-colors: rgba(28, 31, 37, 0.2) rgba(255, 255, 255, 0.2);
-moz-box-align: center;
min-height: 30px;
}
.detail-row-value {
@ -919,6 +926,25 @@
rgba(135, 135, 135, 0));
}
setting[first-row="true"] {
margin-top: 2em;
}
setting {
display: -moz-grid-line;
}
.preferences-description {
font-size: 90.9%;
color: graytext;
margin-top: -2px;
-moz-margin-start: 2em;
}
setting[type="string"] > .setting-input > textbox {
-moz-box-flex: 1;
}
/*** creator ***/
@ -1082,7 +1108,9 @@
/*** buttons ***/
.addon-control {
.addon-control,
setting[type="control"] button,
setting[type="control"] menulist {
-moz-appearance: none;
padding: 1px 4px;
min-width: 60px;
@ -1099,7 +1127,9 @@
display: none;
}
.addon-control:active:hover {
.addon-control:active:hover,
setting[type="control"] button:hover,
setting[type="control"] menulist:hover {
box-shadow: inset 0 1px 3px rgba(0,0,0,.2), 0 1px rgba(255,255,255,0.25);
background-image: -moz-linear-gradient(rgba(45,54,71,0.3), rgba(45,54,71,0.1));
border-color: rgba(60,73,97,0.7);

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

@ -919,16 +919,23 @@
margin-bottom: 2em;
}
#detail-grid > columns > column:first-child {
max-width: 25em;
}
.detail-row[first-row="true"],
.detail-row-complex[first-row="true"] {
.detail-row-complex[first-row="true"],
setting[first-row="true"] {
border-top: none;
}
.detail-row,
.detail-row-complex {
.detail-row-complex,
setting {
border-top: 2px solid;
-moz-border-top-colors: rgba(28, 31, 37, 0.1) rgba(255, 255, 255, 0.1);
-moz-box-align: center;
min-height: 30px;
}
.detail-row-value {
@ -944,6 +951,29 @@
rgba(135, 135, 135, 0));
}
setting[first-row="true"] {
margin-top: 2em;
}
setting {
display: -moz-grid-line;
}
.preferences-description {
font-size: 90.9%;
color: graytext;
margin-top: -2px;
-moz-margin-start: 2em;
}
setting[type="string"] > .setting-input > textbox {
-moz-box-flex: 1;
}
menulist { /* Fixes some styling inconsistencies */
margin: 1px 5px 2px 5px;
}
/*** creator ***/
.creator > label {
@ -1125,7 +1155,9 @@
/*** buttons ***/
.addon-control {
.addon-control,
setting[type="control"] button,
setting[type="control"] menulist {
-moz-appearance: none;
color: black;
padding: 0 5px;
@ -1139,13 +1171,16 @@
0 0 2px 1px rgba(255, 255, 255, 0.25) inset;
}
.addon-control:active:hover {
.addon-control:active:hover,
setting[type="control"] button:hover,
setting[type="control"] menulist:hover {
background-color: rgba(61, 76, 92, 0.2);
border-color: rgba(39, 53, 68, 0.5);
box-shadow: 0 0 3px 1px rgba(39, 53, 68, 0.2) inset;
}
.addon-control > .button-box {
.addon-control > .button-box,
setting[type="control"] button > .button-box {
padding: 1px;
}