1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/modules/CharsetMenu.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,262 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = [ "CharsetMenu" ]; 1.9 + 1.10 +const { classes: Cc, interfaces: Ci, utils: Cu} = Components; 1.11 + 1.12 +Cu.import("resource://gre/modules/Services.jsm"); 1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 +XPCOMUtils.defineLazyGetter(this, "gBundle", function() { 1.15 + const kUrl = "chrome://global/locale/charsetMenu.properties"; 1.16 + return Services.strings.createBundle(kUrl); 1.17 +}); 1.18 + 1.19 +const kAutoDetectors = [ 1.20 + ["off", ""], 1.21 + ["ja", "ja_parallel_state_machine"], 1.22 + ["ru", "ruprob"], 1.23 + ["uk", "ukprob"] 1.24 +]; 1.25 + 1.26 +/** 1.27 + * This set contains encodings that are in the Encoding Standard, except: 1.28 + * - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be 1.29 + * too common not to be included). 1.30 + * - x-user-defined, which practically never makes sense as an end-user-chosen 1.31 + * override. 1.32 + * - Encodings that IE11 doesn't have in its correspoding menu. 1.33 + */ 1.34 +const kEncodings = new Set([ 1.35 + // Globally relevant 1.36 + "UTF-8", 1.37 + "windows-1252", 1.38 + // Arabic 1.39 + "windows-1256", 1.40 + "ISO-8859-6", 1.41 + // Baltic 1.42 + "windows-1257", 1.43 + "ISO-8859-4", 1.44 + // "ISO-8859-13", // Hidden since not in menu in IE11 1.45 + // Central European 1.46 + "windows-1250", 1.47 + "ISO-8859-2", 1.48 + // Chinese, Simplified 1.49 + "gbk", 1.50 + // Chinese, Traditional 1.51 + "Big5", 1.52 + // Cyrillic 1.53 + "windows-1251", 1.54 + "ISO-8859-5", 1.55 + "KOI8-R", 1.56 + "KOI8-U", 1.57 + "IBM866", // Not in menu in Chromium. Maybe drop this? 1.58 + // "x-mac-cyrillic", // Not in menu in IE11 or Chromium. 1.59 + // Greek 1.60 + "windows-1253", 1.61 + "ISO-8859-7", 1.62 + // Hebrew 1.63 + "windows-1255", 1.64 + "ISO-8859-8", 1.65 + // Japanese 1.66 + "Shift_JIS", 1.67 + "EUC-JP", 1.68 + "ISO-2022-JP", 1.69 + // Korean 1.70 + "EUC-KR", 1.71 + // Thai 1.72 + "windows-874", 1.73 + // Turkish 1.74 + "windows-1254", 1.75 + // Vietnamese 1.76 + "windows-1258", 1.77 + // Hiding rare European encodings that aren't in the menu in IE11 and would 1.78 + // make the menu messy by sorting all over the place 1.79 + // "ISO-8859-3", 1.80 + // "ISO-8859-10", 1.81 + // "ISO-8859-14", 1.82 + // "ISO-8859-15", 1.83 + // "ISO-8859-16", 1.84 + // "macintosh" 1.85 +]); 1.86 + 1.87 +// Always at the start of the menu, in this order, followed by a separator. 1.88 +const kPinned = [ 1.89 + "UTF-8", 1.90 + "windows-1252" 1.91 +]; 1.92 + 1.93 +kPinned.forEach(x => kEncodings.delete(x)); 1.94 + 1.95 +function CharsetComparator(a, b) { 1.96 + // Normal sorting sorts the part in parenthesis in an order that 1.97 + // happens to make the less frequently-used items first. 1.98 + let titleA = a.label.replace(/\(.*/, "") + b.value; 1.99 + let titleB = b.label.replace(/\(.*/, "") + a.value; 1.100 + // Secondarily reverse sort by encoding name to sort "windows" or 1.101 + // "shift_jis" first. 1.102 + return titleA.localeCompare(titleB) || b.value.localeCompare(a.value); 1.103 +} 1.104 + 1.105 +function SetDetector(event) { 1.106 + let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); 1.107 + str.data = event.target.getAttribute("detector"); 1.108 + Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str); 1.109 +} 1.110 + 1.111 +function UpdateDetectorMenu(event) { 1.112 + event.stopPropagation(); 1.113 + let detector = Services.prefs.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString); 1.114 + let menuitem = this.getElementsByAttribute("detector", detector).item(0); 1.115 + if (menuitem) { 1.116 + menuitem.setAttribute("checked", "true"); 1.117 + } 1.118 +} 1.119 + 1.120 +let gDetectorInfoCache, gCharsetInfoCache, gPinnedInfoCache; 1.121 + 1.122 +let CharsetMenu = { 1.123 + build: function(parent, showAccessKeys=true, showDetector=true) { 1.124 + function createDOMNode(doc, nodeInfo) { 1.125 + let node = doc.createElement("menuitem"); 1.126 + node.setAttribute("type", "radio"); 1.127 + node.setAttribute("name", nodeInfo.name + "Group"); 1.128 + node.setAttribute(nodeInfo.name, nodeInfo.value); 1.129 + node.setAttribute("label", nodeInfo.label); 1.130 + if (showAccessKeys && nodeInfo.accesskey) { 1.131 + node.setAttribute("accesskey", nodeInfo.accesskey); 1.132 + } 1.133 + return node; 1.134 + } 1.135 + 1.136 + if (parent.hasChildNodes()) { 1.137 + // Detector menu or charset menu already built 1.138 + return; 1.139 + } 1.140 + this._ensureDataReady(); 1.141 + let doc = parent.ownerDocument; 1.142 + 1.143 + if (showDetector) { 1.144 + let menuNode = doc.createElement("menu"); 1.145 + menuNode.setAttribute("label", gBundle.GetStringFromName("charsetMenuAutodet")); 1.146 + if (showAccessKeys) { 1.147 + menuNode.setAttribute("accesskey", gBundle.GetStringFromName("charsetMenuAutodet.key")); 1.148 + } 1.149 + parent.appendChild(menuNode); 1.150 + 1.151 + let menuPopupNode = doc.createElement("menupopup"); 1.152 + menuNode.appendChild(menuPopupNode); 1.153 + menuPopupNode.addEventListener("command", SetDetector); 1.154 + menuPopupNode.addEventListener("popupshown", UpdateDetectorMenu); 1.155 + 1.156 + gDetectorInfoCache.forEach(detectorInfo => menuPopupNode.appendChild(createDOMNode(doc, detectorInfo))); 1.157 + parent.appendChild(doc.createElement("menuseparator")); 1.158 + } 1.159 + 1.160 + gPinnedInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo))); 1.161 + parent.appendChild(doc.createElement("menuseparator")); 1.162 + gCharsetInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo))); 1.163 + }, 1.164 + 1.165 + getData: function() { 1.166 + this._ensureDataReady(); 1.167 + return { 1.168 + detectors: gDetectorInfoCache, 1.169 + pinnedCharsets: gPinnedInfoCache, 1.170 + otherCharsets: gCharsetInfoCache 1.171 + }; 1.172 + }, 1.173 + 1.174 + _ensureDataReady: function() { 1.175 + if (!gDetectorInfoCache) { 1.176 + gDetectorInfoCache = this.getDetectorInfo(); 1.177 + gPinnedInfoCache = this.getCharsetInfo(kPinned, false); 1.178 + gCharsetInfoCache = this.getCharsetInfo(kEncodings); 1.179 + } 1.180 + }, 1.181 + 1.182 + getDetectorInfo: function() { 1.183 + return kAutoDetectors.map(([detectorName, nodeId]) => ({ 1.184 + label: this._getDetectorLabel(detectorName), 1.185 + accesskey: this._getDetectorAccesskey(detectorName), 1.186 + name: "detector", 1.187 + value: nodeId 1.188 + })); 1.189 + }, 1.190 + 1.191 + getCharsetInfo: function(charsets, sort=true) { 1.192 + let list = [{ 1.193 + label: this._getCharsetLabel(charset), 1.194 + accesskey: this._getCharsetAccessKey(charset), 1.195 + name: "charset", 1.196 + value: charset 1.197 + } for (charset of charsets)]; 1.198 + 1.199 + if (sort) { 1.200 + list.sort(CharsetComparator); 1.201 + } 1.202 + return list; 1.203 + }, 1.204 + 1.205 + _getDetectorLabel: function(detector) { 1.206 + try { 1.207 + return gBundle.GetStringFromName("charsetMenuAutodet." + detector); 1.208 + } catch (ex) {} 1.209 + return detector; 1.210 + }, 1.211 + _getDetectorAccesskey: function(detector) { 1.212 + try { 1.213 + return gBundle.GetStringFromName("charsetMenuAutodet." + detector + ".key"); 1.214 + } catch (ex) {} 1.215 + return ""; 1.216 + }, 1.217 + 1.218 + _getCharsetLabel: function(charset) { 1.219 + if (charset == "gbk") { 1.220 + // Localization key has been revised 1.221 + charset = "gbk.bis"; 1.222 + } 1.223 + try { 1.224 + return gBundle.GetStringFromName(charset); 1.225 + } catch (ex) {} 1.226 + return charset; 1.227 + }, 1.228 + _getCharsetAccessKey: function(charset) { 1.229 + if (charset == "gbk") { 1.230 + // Localization key has been revised 1.231 + charset = "gbk.bis"; 1.232 + } 1.233 + try { 1.234 + return gBundle.GetStringFromName(charset + ".key"); 1.235 + } catch (ex) {} 1.236 + return ""; 1.237 + }, 1.238 + 1.239 + /** 1.240 + * For substantially similar encodings, treat two encodings as the same 1.241 + * for the purpose of the check mark. 1.242 + */ 1.243 + foldCharset: function(charset) { 1.244 + switch (charset) { 1.245 + case "ISO-8859-8-I": 1.246 + return "windows-1255"; 1.247 + 1.248 + case "gb18030": 1.249 + return "gbk"; 1.250 + 1.251 + default: 1.252 + return charset; 1.253 + } 1.254 + }, 1.255 + 1.256 + update: function(parent, charset) { 1.257 + let menuitem = parent.getElementsByAttribute("charset", this.foldCharset(charset)).item(0); 1.258 + if (menuitem) { 1.259 + menuitem.setAttribute("checked", "true"); 1.260 + } 1.261 + }, 1.262 +}; 1.263 + 1.264 +Object.freeze(CharsetMenu); 1.265 +