зеркало из https://github.com/mozilla/pjs.git
Bug 653637 - Provide a simple way for addons to have preferences UI - Part 2; r=dtownsend ui-r=jboriss
This commit is contained in:
Родитель
db9550f627
Коммит
718a9be9dd
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче