|
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/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = [ "CharsetMenu" ]; |
|
6 |
|
7 const { classes: Cc, interfaces: Ci, utils: Cu} = Components; |
|
8 |
|
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 }); |
|
15 |
|
16 const kAutoDetectors = [ |
|
17 ["off", ""], |
|
18 ["ja", "ja_parallel_state_machine"], |
|
19 ["ru", "ruprob"], |
|
20 ["uk", "ukprob"] |
|
21 ]; |
|
22 |
|
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 ]); |
|
83 |
|
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 ]; |
|
89 |
|
90 kPinned.forEach(x => kEncodings.delete(x)); |
|
91 |
|
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 } |
|
101 |
|
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 } |
|
107 |
|
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 } |
|
116 |
|
117 let gDetectorInfoCache, gCharsetInfoCache, gPinnedInfoCache; |
|
118 |
|
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 } |
|
132 |
|
133 if (parent.hasChildNodes()) { |
|
134 // Detector menu or charset menu already built |
|
135 return; |
|
136 } |
|
137 this._ensureDataReady(); |
|
138 let doc = parent.ownerDocument; |
|
139 |
|
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); |
|
147 |
|
148 let menuPopupNode = doc.createElement("menupopup"); |
|
149 menuNode.appendChild(menuPopupNode); |
|
150 menuPopupNode.addEventListener("command", SetDetector); |
|
151 menuPopupNode.addEventListener("popupshown", UpdateDetectorMenu); |
|
152 |
|
153 gDetectorInfoCache.forEach(detectorInfo => menuPopupNode.appendChild(createDOMNode(doc, detectorInfo))); |
|
154 parent.appendChild(doc.createElement("menuseparator")); |
|
155 } |
|
156 |
|
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 }, |
|
161 |
|
162 getData: function() { |
|
163 this._ensureDataReady(); |
|
164 return { |
|
165 detectors: gDetectorInfoCache, |
|
166 pinnedCharsets: gPinnedInfoCache, |
|
167 otherCharsets: gCharsetInfoCache |
|
168 }; |
|
169 }, |
|
170 |
|
171 _ensureDataReady: function() { |
|
172 if (!gDetectorInfoCache) { |
|
173 gDetectorInfoCache = this.getDetectorInfo(); |
|
174 gPinnedInfoCache = this.getCharsetInfo(kPinned, false); |
|
175 gCharsetInfoCache = this.getCharsetInfo(kEncodings); |
|
176 } |
|
177 }, |
|
178 |
|
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 }, |
|
187 |
|
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)]; |
|
195 |
|
196 if (sort) { |
|
197 list.sort(CharsetComparator); |
|
198 } |
|
199 return list; |
|
200 }, |
|
201 |
|
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 }, |
|
214 |
|
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 }, |
|
235 |
|
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"; |
|
244 |
|
245 case "gb18030": |
|
246 return "gbk"; |
|
247 |
|
248 default: |
|
249 return charset; |
|
250 } |
|
251 }, |
|
252 |
|
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 }; |
|
260 |
|
261 Object.freeze(CharsetMenu); |
|
262 |