Fix bug 321010 - Need better stripping of illegal css chars from category names. r=philipp, p=gekachecka

This commit is contained in:
mozilla%kewis.ch 2008-02-02 20:12:55 +00:00
Родитель 7f6cc7bd4f
Коммит f10f64d1fd
8 изменённых файлов: 179 добавлений и 1276 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -168,12 +168,47 @@ function calendarListInitCategoryColors() {
var categoryPrefBranch = prefService.getBranch("calendar.category.color.");
var categories = categoryPrefBranch.getChildList("", {});
// check category preference name syntax
categories = calendarConvertObsoleteColorPrefs(categoryPrefBranch, categories);
// Update all categories
for each (var category in categories) {
updateStyleSheetForObject(category, gCachedStyleSheet);
}
}
/**
* Remove illegally formatted category names from the array coloredCategories
* so they don't cause CSS errors. For each illegal colored category c, if
* its color preference has not yet been replaced with a converted preference
* with key formatStringForCSSRule(c), create the preference with the
* converted key and with the previous preference value, and clear the old
* preference. (For most users who upgrade and do not later add colors with a
* downgrade version, this should convert any illegal preferences once, so
* future runs have no illegal preferences.)
* @param categoryPrefBranch prefBranch for "calendar.category.color."
* @param coloredCategories array of preference name suffixes under the prefBranch.
* @return same array with each illegal name replaced with formatted name if
* it doesn't already exist, or simply removed from array if it does.
*/
function calendarConvertObsoleteColorPrefs(categoryPrefBranch, coloredCategories) {
for (var i in coloredCategories) {
var category = coloredCategories[i];
if (category.search(/[^_0-9a-z-]/) != -1) {
var categoryFix = formatStringForCSSRule(category);
if (!categoryPrefBranch.prefHasUserValue(categoryFix)) {
var color = categoryPrefBranch.getCharPref(category);
categoryPrefBranch.setCharPref(categoryFix, color);
categoryPrefBranch.clearUserPref(category); // not usable
coloredCategories[i] = categoryFix; // replace illegal name
} else {
coloredCategories.splice(i, 1); // remove illegal name
}
}
}
return coloredCategories;
}
function calendarListUpdateColor(aCalendar) {
var selectorPrefix = "treechildren::-moz-tree-cell";

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

@ -522,14 +522,13 @@
}
var categoriesSelectorList = "";
if (itd.item.getProperty("CATEGORIES") != null) {
var categoriesList = itd.item.getProperty("CATEGORIES").split(",");
for (var i = 0; i < categoriesList.length; i++ ) {
// Remove illegal chars.
categoriesList[i] = formatStringForCSSRule(categoriesList[i]);
}
categoriesSelectorList = categoriesList.join(" ");
var categoriesProperty = itd.item.getProperty("CATEGORIES");
if (categoriesProperty) {
var categoriesArray = categoriesStringToArray(categoriesProperty);
var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
categoriesSelectorList = cssClassesArray.join(" ");
}
box.setAttribute("item-category", categoriesSelectorList);
this.dayitems.insertBefore(box, before);

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

@ -206,13 +206,11 @@
container.setAttribute("item-calendar", item.calendar.uri.spec);
var categoriesSelectorList = "";
if (item.getProperty("CATEGORIES") != null) {
var categoriesList = item.getProperty("CATEGORIES").split(",");
for (var i = 0; i < categoriesList.length; i++ ) {
// Remove illegal chars.
categoriesList[i] = formatStringForCSSRule(categoriesList[i]);
}
categoriesSelectorList = categoriesList.join(" ");
var categoriesProperty = item.getProperty("CATEGORIES");
if (categoriesProperty) {
var categoriesArray = categoriesStringToArray(categoriesProperty);
var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
categoriesSelectorList = cssClassesArray.join(" ");
}
var box = document.

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

@ -410,13 +410,18 @@ function getStyleSheet(aStyleSheetPath) {
return null;
}
// Updates the style rules for a particular object. If the object is a
// category (and hence doesn't have a uri), we set the border color. If
// it's a calendar, we set the background color
/**
* Updates the style rules for a particular object. If the object is a
* category (and hence doesn't have a uri), we set the category bar color.
* If it's a calendar, we set the background color and contrasting text color.
* @param aObject either a calendar (with a .uri), or the category color
* pref key suffix [the non-unicode part after "calendar.category.color.",
* equivalent to formatStringForCSSRule(categoryNameInUnicode)].
*/
function updateStyleSheetForObject(aObject, aSheet) {
var selectorPrefix, name, ruleUpdaterFunc;
if (aObject.uri) {
// This is a calendar, so we're going to set the background color
// For a calendar, set background and contrasting text colors
name = aObject.uri.spec;
selectorPrefix = "item-calendar=";
ruleUpdaterFunc = function calendarRuleFunc(aRule, aIndex) {
@ -428,12 +433,12 @@ function updateStyleSheetForObject(aObject, aSheet) {
aRule.style.color = getContrastingTextColor(color);
};
} else {
// This is a category, where we set the border. Also note that we
// use the ~= selector, since there could be multiple categories
name = formatStringForCSSRule(aObject);
// For a category, set the category bar color. Also note that
// it uses the ~= selector, since there could be multiple categories.
name = aObject;
selectorPrefix = "item-category~=";
ruleUpdaterFunc = function categoryRuleFunc(aRule, aIndex) {
var color = getPrefSafe("calendar.category.color."+aObject, null);
var color = getPrefSafe("calendar.category.color."+name, null);
if (color) {
aRule.style.backgroundColor = color;
} else {

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

@ -67,14 +67,7 @@ var gCategoriesPane = {
}
var categories = document.getElementById("calendar.categories.names").value;
// If no categories are configured load a default set from properties file
if (!categories || categories == "") {
categories = calGetString("categories", "categories");
document.getElementById("calendar.categories.names").value = categories;
}
gCategoryList = categories.split(",");
gCategoryList = categoriesStringToArray(categories);
// When categories is empty, split returns an array containing one empty
// string, rather than an empty array. This results in an empty listbox
@ -87,8 +80,10 @@ var gCategoriesPane = {
},
updateCategoryList: function () {
gCategoryList.sort();
document.getElementById("calendar.categories.names").value = gCategoryList.join(",");
sortArrayByLocaleCollator(gCategoryList);
document.getElementById("calendar.categories.names").value =
categoriesArrayToString(gCategoryList);
var listbox = document.getElementById("categorieslist");
listbox.clearSelection();

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

@ -166,12 +166,40 @@ function calendarDefaultTimezone() {
/**
* Format the given string to work inside a CSS rule selector
* (and as part of a non-unicode preference key).
*
* @param aString The string to format
* @return The formatted string
* Replaces each space ' ' char with '_'.
* Replaces each char other than ascii digits and letters, with '-uxHHH-'
* where HHH is unicode in hexadecimal (variable length, terminated by the '-').
*
* Ensures: result only contains ascii digits, letters,'-', and '_'.
* Ensures: result is invertible, so (f(a) = f(b)) implies (a = b).
* also means f is not idempotent, so (a != f(a)) implies (f(a) != f(f(a))).
* Ensures: result must be lowercase.
* Rationale: preference keys require 8bit chars, and ascii chars are legible
* in most fonts (in case user edits PROFILE/prefs.js).
* CSS class names in Gecko 1.8 seem to require lowercase,
* no punctuation, and of course no spaces.
* nmchar [_a-zA-Z0-9-]|{nonascii}|{escape}
* name {nmchar}+
* http://www.w3.org/TR/CSS21/grammar.html#scanner
*
* @param aString The unicode string to format
* @return The formatted string using only chars [_a-zA-Z0-9-]
*/
function formatStringForCSSRule(aString) {
return aString.replace(/ /g, "_").toLowerCase();
function toReplacement(ch) {
// char code is natural number (positive integer)
var nat = ch.charCodeAt(0);
switch(nat) {
case 0x20: // space
return "_";
default:
return "-ux" + nat.toString(16) + "-"; // lowercase
}
}
// Result must be lowercase or style rule will not work.
return aString.toLowerCase().replace(/[^a-zA-Z0-9]/g, toReplacement);
}
/**
@ -979,6 +1007,80 @@ function getLocalizedPref(aPrefName, aDefault) {
return result;
}
/**
* Get array of category names from preferences or locale default,
* unescaping any commas in each category name.
* @return array of category names
*/
function getPrefCategoriesArray() {
var categories = getLocalizedPref("calendar.categories.names", null);
// If no categories are configured load a default set from properties file
if (!categories || categories == "") {
categories = calGetString("categories", "categories");
setLocalizedPref("calendar.categories.names", categories);
}
return categoriesStringToArray(categories);
}
/**
* Convert categories string to list of category names.
*
* Stored categories may include escaped commas within a name.
* Split categories string at commas, but not at escaped commas (\,).
* Afterward, replace escaped commas (\,) with commas (,) in each name.
* @param aCategoriesPrefValue string from "calendar.categories.names" pref,
* which may contain escaped commas (\,) in names.
* @return list of category names
*/
function categoriesStringToArray(aCategories) {
// \u001A is the unicode "SUBSTITUTE" character
function revertCommas(name) { return name.replace(/\u001A/g, ","); }
return aCategories.replace(/\\,/g, "\u001A").split(",").map(revertCommas);
}
/**
* Set categories preference, escaping any commas in category names.
* @param aCategoriesArray array of category names,
* may contain unescaped commas which will be escaped in combined pref.
*/
function setPrefCategoriesFromArray(aCategoriesArray) {
setLocalizedPref("calendar.categories.names",
categoriesArrayToString(aCategoriesList));
}
/**
* Convert array of category names to string.
*
* Category names may contain commas (,). Escape commas (\,) in each,
* then join them in comma separated string for storage.
* @param aSortedCategoriesArray sorted array of category names,
* may contain unescaped commas, which will be escaped in combined string.
*/
function categoriesArrayToString(aSortedCategoriesArray) {
function escapeComma(category) { return category.replace(/,/g,"\\,"); }
return aSortedCategoriesArray.map(escapeComma).join(",");
}
/**
* Sort an array of strings according to the current locale.
* Modifies aStringArray, returning it sorted.
*/
function sortArrayByLocaleCollator(aStringArray) {
// get a current locale string collator for compareEvents
var localeService =
Components
.classes["@mozilla.org/intl/nslocaleservice;1"]
.getService(Components.interfaces.nsILocaleService);
var localeCollator =
Components
.classes["@mozilla.org/intl/collation-factory;1"]
.getService(Components.interfaces.nsICollationFactory)
.CreateCollation(localeService.getApplicationLocale());
function compare(a, b) { return localeCollator.compareString(0, a, b); }
aStringArray.sort(compare);
return aStringArray;
}
/**
* Gets the value of a string in a .properties file
*

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

@ -335,15 +335,7 @@ function loadDialog(item) {
}
// Categories
var categoriesString = getLocalizedPref("calendar.categories.names", "");
// If no categories are configured load a default set from properties file
if (!categoriesString || categoriesString == "") {
categoriesString = calGetString("categories", "categories");
setLocalizedPref("calendar.categories.names", categoriesString);
}
var categoriesList = categoriesString.split(",");
var categoriesList = getPrefCategoriesArray();
// When categoriesString is empty, split returns an array containing one
// empty string, rather than an empty array. This results in an empty
@ -353,13 +345,16 @@ function loadDialog(item) {
}
// insert the category already in the menulist so it doesn't get lost
var itemCategory = item.getProperty("CATEGORIES");
if (itemCategory) {
if (categoriesString.indexOf(itemCategory) == -1) {
categoriesList[categoriesList.length] = itemCategory;
var itemProperty = item.getProperty("CATEGORIES");
if (itemProperty) {
var itemCategories = categoriesStringToArray(itemProperty);
for each (var itemCategory in itemCategories) {
if (!categoriesList.some(function(cat){ return cat == itemCategory; })){
categoriesList.push(itemCategory);
}
}
}
categoriesList.sort();
sortArrayByLocaleCollator(categoriesList);
var oldMenulist = document.getElementById("item-categories");
while (oldMenulist.hasChildNodes()) {
@ -818,7 +813,7 @@ function saveDialog(item) {
var category = getElementValue("item-categories");
if (category != "NONE") {
setItemProperty(item, "CATEGORIES", category);
setItemProperty(item, "CATEGORIES", categoriesArrayToString([category]));
} else {
item.deleteProperty("CATEGORIES");
}