| |
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 |