summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/CharsetMenu.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/CharsetMenu.jsm')
-rw-r--r--toolkit/modules/CharsetMenu.jsm267
1 files changed, 267 insertions, 0 deletions
diff --git a/toolkit/modules/CharsetMenu.jsm b/toolkit/modules/CharsetMenu.jsm
new file mode 100644
index 000000000..f6479c024
--- /dev/null
+++ b/toolkit/modules/CharsetMenu.jsm
@@ -0,0 +1,267 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [ "CharsetMenu" ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "gBundle", function() {
+ const kUrl = "chrome://global/locale/charsetMenu.properties";
+ return Services.strings.createBundle(kUrl);
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+const kAutoDetectors = [
+ ["off", ""],
+ ["ja", "ja_parallel_state_machine"],
+ ["ru", "ruprob"],
+ ["uk", "ukprob"]
+];
+
+/**
+ * This set contains encodings that are in the Encoding Standard, except:
+ * - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be
+ * too common not to be included).
+ * - x-user-defined, which practically never makes sense as an end-user-chosen
+ * override.
+ * - Encodings that IE11 doesn't have in its correspoding menu.
+ */
+const kEncodings = new Set([
+ // Globally relevant
+ "UTF-8",
+ "windows-1252",
+ // Arabic
+ "windows-1256",
+ "ISO-8859-6",
+ // Baltic
+ "windows-1257",
+ "ISO-8859-4",
+ // "ISO-8859-13", // Hidden since not in menu in IE11
+ // Central European
+ "windows-1250",
+ "ISO-8859-2",
+ // Chinese, Simplified
+ "gbk",
+ // Chinese, Traditional
+ "Big5",
+ // Cyrillic
+ "windows-1251",
+ "ISO-8859-5",
+ "KOI8-R",
+ "KOI8-U",
+ "IBM866", // Not in menu in Chromium. Maybe drop this?
+ // "x-mac-cyrillic", // Not in menu in IE11 or Chromium.
+ // Greek
+ "windows-1253",
+ "ISO-8859-7",
+ // Hebrew
+ "windows-1255",
+ "ISO-8859-8",
+ // Japanese
+ "Shift_JIS",
+ "EUC-JP",
+ "ISO-2022-JP",
+ // Korean
+ "EUC-KR",
+ // Thai
+ "windows-874",
+ // Turkish
+ "windows-1254",
+ // Vietnamese
+ "windows-1258",
+ // Hiding rare European encodings that aren't in the menu in IE11 and would
+ // make the menu messy by sorting all over the place
+ // "ISO-8859-3",
+ // "ISO-8859-10",
+ // "ISO-8859-14",
+ // "ISO-8859-15",
+ // "ISO-8859-16",
+ // "macintosh"
+]);
+
+// Always at the start of the menu, in this order, followed by a separator.
+const kPinned = [
+ "UTF-8",
+ "windows-1252"
+];
+
+kPinned.forEach(x => kEncodings.delete(x));
+
+function CharsetComparator(a, b) {
+ // Normal sorting sorts the part in parenthesis in an order that
+ // happens to make the less frequently-used items first.
+ let titleA = a.label.replace(/\(.*/, "") + b.value;
+ let titleB = b.label.replace(/\(.*/, "") + a.value;
+ // Secondarily reverse sort by encoding name to sort "windows" or
+ // "shift_jis" first.
+ return titleA.localeCompare(titleB) || b.value.localeCompare(a.value);
+}
+
+function SetDetector(event) {
+ let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ str.data = event.target.getAttribute("detector");
+ Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
+}
+
+function UpdateDetectorMenu(event) {
+ event.stopPropagation();
+ let detector = Services.prefs.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString);
+ let menuitem = this.getElementsByAttribute("detector", detector).item(0);
+ if (menuitem) {
+ menuitem.setAttribute("checked", "true");
+ }
+}
+
+var gDetectorInfoCache, gCharsetInfoCache, gPinnedInfoCache;
+
+var CharsetMenu = {
+ build: function(parent, deprecatedShowAccessKeys=true, showDetector=true) {
+ if (!deprecatedShowAccessKeys) {
+ Deprecated.warning("CharsetMenu no longer supports building a menu with no access keys.",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1088710");
+ }
+ function createDOMNode(doc, nodeInfo) {
+ let node = doc.createElement("menuitem");
+ node.setAttribute("type", "radio");
+ node.setAttribute("name", nodeInfo.name + "Group");
+ node.setAttribute(nodeInfo.name, nodeInfo.value);
+ node.setAttribute("label", nodeInfo.label);
+ if (nodeInfo.accesskey) {
+ node.setAttribute("accesskey", nodeInfo.accesskey);
+ }
+ return node;
+ }
+
+ if (parent.hasChildNodes()) {
+ // Detector menu or charset menu already built
+ return;
+ }
+ this._ensureDataReady();
+ let doc = parent.ownerDocument;
+
+ if (showDetector) {
+ let menuNode = doc.createElement("menu");
+ menuNode.setAttribute("label", gBundle.GetStringFromName("charsetMenuAutodet"));
+ menuNode.setAttribute("accesskey", gBundle.GetStringFromName("charsetMenuAutodet.key"));
+ parent.appendChild(menuNode);
+
+ let menuPopupNode = doc.createElement("menupopup");
+ menuNode.appendChild(menuPopupNode);
+ menuPopupNode.addEventListener("command", SetDetector);
+ menuPopupNode.addEventListener("popupshown", UpdateDetectorMenu);
+
+ gDetectorInfoCache.forEach(detectorInfo => menuPopupNode.appendChild(createDOMNode(doc, detectorInfo)));
+ parent.appendChild(doc.createElement("menuseparator"));
+ }
+
+ gPinnedInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
+ parent.appendChild(doc.createElement("menuseparator"));
+ gCharsetInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
+ },
+
+ getData: function() {
+ this._ensureDataReady();
+ return {
+ detectors: gDetectorInfoCache,
+ pinnedCharsets: gPinnedInfoCache,
+ otherCharsets: gCharsetInfoCache
+ };
+ },
+
+ _ensureDataReady: function() {
+ if (!gDetectorInfoCache) {
+ gDetectorInfoCache = this.getDetectorInfo();
+ gPinnedInfoCache = this.getCharsetInfo(kPinned, false);
+ gCharsetInfoCache = this.getCharsetInfo(kEncodings);
+ }
+ },
+
+ getDetectorInfo: function() {
+ return kAutoDetectors.map(([detectorName, nodeId]) => ({
+ label: this._getDetectorLabel(detectorName),
+ accesskey: this._getDetectorAccesskey(detectorName),
+ name: "detector",
+ value: nodeId
+ }));
+ },
+
+ getCharsetInfo: function(charsets, sort=true) {
+ let list = Array.from(charsets, charset => ({
+ label: this._getCharsetLabel(charset),
+ accesskey: this._getCharsetAccessKey(charset),
+ name: "charset",
+ value: charset
+ }));
+
+ if (sort) {
+ list.sort(CharsetComparator);
+ }
+ return list;
+ },
+
+ _getDetectorLabel: function(detector) {
+ try {
+ return gBundle.GetStringFromName("charsetMenuAutodet." + detector);
+ } catch (ex) {}
+ return detector;
+ },
+ _getDetectorAccesskey: function(detector) {
+ try {
+ return gBundle.GetStringFromName("charsetMenuAutodet." + detector + ".key");
+ } catch (ex) {}
+ return "";
+ },
+
+ _getCharsetLabel: function(charset) {
+ if (charset == "gbk") {
+ // Localization key has been revised
+ charset = "gbk.bis";
+ }
+ try {
+ return gBundle.GetStringFromName(charset);
+ } catch (ex) {}
+ return charset;
+ },
+ _getCharsetAccessKey: function(charset) {
+ if (charset == "gbk") {
+ // Localization key has been revised
+ charset = "gbk.bis";
+ }
+ try {
+ return gBundle.GetStringFromName(charset + ".key");
+ } catch (ex) {}
+ return "";
+ },
+
+ /**
+ * For substantially similar encodings, treat two encodings as the same
+ * for the purpose of the check mark.
+ */
+ foldCharset: function(charset) {
+ switch (charset) {
+ case "ISO-8859-8-I":
+ return "windows-1255";
+
+ case "gb18030":
+ return "gbk";
+
+ default:
+ return charset;
+ }
+ },
+
+ update: function(parent, charset) {
+ let menuitem = parent.getElementsByAttribute("charset", this.foldCharset(charset)).item(0);
+ if (menuitem) {
+ menuitem.setAttribute("checked", "true");
+ }
+ },
+};
+
+Object.freeze(CharsetMenu);
+