Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 this.EXPORTED_SYMBOLS = [ "CharsetMenu" ];
7 const { classes: Cc, interfaces: Ci, utils: Cu} = Components;
9 Cu.import("resource://gre/modules/Services.jsm");
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
11 XPCOMUtils.defineLazyGetter(this, "gBundle", function() {
12 const kUrl = "chrome://global/locale/charsetMenu.properties";
13 return Services.strings.createBundle(kUrl);
14 });
16 const kAutoDetectors = [
17 ["off", ""],
18 ["ja", "ja_parallel_state_machine"],
19 ["ru", "ruprob"],
20 ["uk", "ukprob"]
21 ];
23 /**
24 * This set contains encodings that are in the Encoding Standard, except:
25 * - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be
26 * too common not to be included).
27 * - x-user-defined, which practically never makes sense as an end-user-chosen
28 * override.
29 * - Encodings that IE11 doesn't have in its correspoding menu.
30 */
31 const kEncodings = new Set([
32 // Globally relevant
33 "UTF-8",
34 "windows-1252",
35 // Arabic
36 "windows-1256",
37 "ISO-8859-6",
38 // Baltic
39 "windows-1257",
40 "ISO-8859-4",
41 // "ISO-8859-13", // Hidden since not in menu in IE11
42 // Central European
43 "windows-1250",
44 "ISO-8859-2",
45 // Chinese, Simplified
46 "gbk",
47 // Chinese, Traditional
48 "Big5",
49 // Cyrillic
50 "windows-1251",
51 "ISO-8859-5",
52 "KOI8-R",
53 "KOI8-U",
54 "IBM866", // Not in menu in Chromium. Maybe drop this?
55 // "x-mac-cyrillic", // Not in menu in IE11 or Chromium.
56 // Greek
57 "windows-1253",
58 "ISO-8859-7",
59 // Hebrew
60 "windows-1255",
61 "ISO-8859-8",
62 // Japanese
63 "Shift_JIS",
64 "EUC-JP",
65 "ISO-2022-JP",
66 // Korean
67 "EUC-KR",
68 // Thai
69 "windows-874",
70 // Turkish
71 "windows-1254",
72 // Vietnamese
73 "windows-1258",
74 // Hiding rare European encodings that aren't in the menu in IE11 and would
75 // make the menu messy by sorting all over the place
76 // "ISO-8859-3",
77 // "ISO-8859-10",
78 // "ISO-8859-14",
79 // "ISO-8859-15",
80 // "ISO-8859-16",
81 // "macintosh"
82 ]);
84 // Always at the start of the menu, in this order, followed by a separator.
85 const kPinned = [
86 "UTF-8",
87 "windows-1252"
88 ];
90 kPinned.forEach(x => kEncodings.delete(x));
92 function CharsetComparator(a, b) {
93 // Normal sorting sorts the part in parenthesis in an order that
94 // happens to make the less frequently-used items first.
95 let titleA = a.label.replace(/\(.*/, "") + b.value;
96 let titleB = b.label.replace(/\(.*/, "") + a.value;
97 // Secondarily reverse sort by encoding name to sort "windows" or
98 // "shift_jis" first.
99 return titleA.localeCompare(titleB) || b.value.localeCompare(a.value);
100 }
102 function SetDetector(event) {
103 let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
104 str.data = event.target.getAttribute("detector");
105 Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
106 }
108 function UpdateDetectorMenu(event) {
109 event.stopPropagation();
110 let detector = Services.prefs.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString);
111 let menuitem = this.getElementsByAttribute("detector", detector).item(0);
112 if (menuitem) {
113 menuitem.setAttribute("checked", "true");
114 }
115 }
117 let gDetectorInfoCache, gCharsetInfoCache, gPinnedInfoCache;
119 let CharsetMenu = {
120 build: function(parent, showAccessKeys=true, showDetector=true) {
121 function createDOMNode(doc, nodeInfo) {
122 let node = doc.createElement("menuitem");
123 node.setAttribute("type", "radio");
124 node.setAttribute("name", nodeInfo.name + "Group");
125 node.setAttribute(nodeInfo.name, nodeInfo.value);
126 node.setAttribute("label", nodeInfo.label);
127 if (showAccessKeys && nodeInfo.accesskey) {
128 node.setAttribute("accesskey", nodeInfo.accesskey);
129 }
130 return node;
131 }
133 if (parent.hasChildNodes()) {
134 // Detector menu or charset menu already built
135 return;
136 }
137 this._ensureDataReady();
138 let doc = parent.ownerDocument;
140 if (showDetector) {
141 let menuNode = doc.createElement("menu");
142 menuNode.setAttribute("label", gBundle.GetStringFromName("charsetMenuAutodet"));
143 if (showAccessKeys) {
144 menuNode.setAttribute("accesskey", gBundle.GetStringFromName("charsetMenuAutodet.key"));
145 }
146 parent.appendChild(menuNode);
148 let menuPopupNode = doc.createElement("menupopup");
149 menuNode.appendChild(menuPopupNode);
150 menuPopupNode.addEventListener("command", SetDetector);
151 menuPopupNode.addEventListener("popupshown", UpdateDetectorMenu);
153 gDetectorInfoCache.forEach(detectorInfo => menuPopupNode.appendChild(createDOMNode(doc, detectorInfo)));
154 parent.appendChild(doc.createElement("menuseparator"));
155 }
157 gPinnedInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
158 parent.appendChild(doc.createElement("menuseparator"));
159 gCharsetInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
160 },
162 getData: function() {
163 this._ensureDataReady();
164 return {
165 detectors: gDetectorInfoCache,
166 pinnedCharsets: gPinnedInfoCache,
167 otherCharsets: gCharsetInfoCache
168 };
169 },
171 _ensureDataReady: function() {
172 if (!gDetectorInfoCache) {
173 gDetectorInfoCache = this.getDetectorInfo();
174 gPinnedInfoCache = this.getCharsetInfo(kPinned, false);
175 gCharsetInfoCache = this.getCharsetInfo(kEncodings);
176 }
177 },
179 getDetectorInfo: function() {
180 return kAutoDetectors.map(([detectorName, nodeId]) => ({
181 label: this._getDetectorLabel(detectorName),
182 accesskey: this._getDetectorAccesskey(detectorName),
183 name: "detector",
184 value: nodeId
185 }));
186 },
188 getCharsetInfo: function(charsets, sort=true) {
189 let list = [{
190 label: this._getCharsetLabel(charset),
191 accesskey: this._getCharsetAccessKey(charset),
192 name: "charset",
193 value: charset
194 } for (charset of charsets)];
196 if (sort) {
197 list.sort(CharsetComparator);
198 }
199 return list;
200 },
202 _getDetectorLabel: function(detector) {
203 try {
204 return gBundle.GetStringFromName("charsetMenuAutodet." + detector);
205 } catch (ex) {}
206 return detector;
207 },
208 _getDetectorAccesskey: function(detector) {
209 try {
210 return gBundle.GetStringFromName("charsetMenuAutodet." + detector + ".key");
211 } catch (ex) {}
212 return "";
213 },
215 _getCharsetLabel: function(charset) {
216 if (charset == "gbk") {
217 // Localization key has been revised
218 charset = "gbk.bis";
219 }
220 try {
221 return gBundle.GetStringFromName(charset);
222 } catch (ex) {}
223 return charset;
224 },
225 _getCharsetAccessKey: function(charset) {
226 if (charset == "gbk") {
227 // Localization key has been revised
228 charset = "gbk.bis";
229 }
230 try {
231 return gBundle.GetStringFromName(charset + ".key");
232 } catch (ex) {}
233 return "";
234 },
236 /**
237 * For substantially similar encodings, treat two encodings as the same
238 * for the purpose of the check mark.
239 */
240 foldCharset: function(charset) {
241 switch (charset) {
242 case "ISO-8859-8-I":
243 return "windows-1255";
245 case "gb18030":
246 return "gbk";
248 default:
249 return charset;
250 }
251 },
253 update: function(parent, charset) {
254 let menuitem = parent.getElementsByAttribute("charset", this.foldCharset(charset)).item(0);
255 if (menuitem) {
256 menuitem.setAttribute("checked", "true");
257 }
258 },
259 };
261 Object.freeze(CharsetMenu);