Bug 611580 - Allow chaning character encoding in Fennec. [r=mfinkle, a=blocking]
authorWes Johnston <wjohnston@mozilla.com>
Fri, 18 Feb 2011 11:48:50 -0800
changeset 67406 b76d94f1bf9ad438e99f26f7f6f1ca2cce07c252
parent 67405 46ef7819b3e77d930661360fe4a25bd5c67c7fda
child 67407 f2cd9ac0c55276343d394a73067e2d1ce18d8f80
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, blocking
bugs611580
Bug 611580 - Allow chaning character encoding in Fennec. [r=mfinkle, a=blocking]
mobile/app/mobile.js
mobile/chrome/content/bindings/browser.js
mobile/chrome/content/bindings/browser.xml
mobile/chrome/content/bindings/setting.xml
mobile/chrome/content/browser-scripts.js
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.css
mobile/chrome/content/browser.xul
mobile/chrome/content/common-ui.js
mobile/chrome/content/content.js
mobile/locales/en-US/chrome/browser.dtd
mobile/locales/en-US/chrome/browser.properties
mobile/locales/en-US/chrome/preferences.dtd
--- a/mobile/app/mobile.js
+++ b/mobile/app/mobile.js
@@ -238,16 +238,21 @@ pref("accessibility.typeaheadfind.linkso
 pref("accessibility.typeaheadfind.casesensitive", 0);
 // zoom key(F7) conflicts with caret browsing on maemo
 pref("accessibility.browsewithcaret_shortcut.enabled", false);
 
 // Whether or not we show a dialog box informing the user that the update was
 // successfully applied.
 pref("app.update.showInstalledUI", false);
 
+// Whether the character encoding menu is under the main Firefox button. This
+// preference is a string so that localizers can alter it.
+pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
+pref("intl.charsetmenu.browser.static", "chrome://browser/locale/browser.properties");
+
 // pointer to the default engine name
 pref("browser.search.defaultenginename", "chrome://browser/locale/region.properties");
 // SSL error page behaviour
 pref("browser.ssl_override_behavior", 2);
 pref("browser.xul.error_pages.expert_bad_cert", false);
 
 // disable logging for the search service by default
 pref("browser.search.log", false);
--- a/mobile/chrome/content/bindings/browser.js
+++ b/mobile/chrome/content/bindings/browser.js
@@ -33,22 +33,25 @@ let WebProgressListener = {
 
   onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI) {
     if (content != aWebProgress.DOMWindow)
       return;
 
     let spec = aLocationURI ? aLocationURI.spec : "";
     let location = spec.split("#")[0];
 
+    let charset = content.document.characterSet;
+
     let json = {
       contentWindowId: content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID,
-      documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec,
-      location: spec,
-      canGoBack: docShell.canGoBack,
-      canGoForward: docShell.canGoForward
+      documentURI:     aWebProgress.DOMWindow.document.documentURIObject.spec,
+      location:        spec,
+      canGoBack:       docShell.canGoBack,
+      canGoForward:    docShell.canGoForward,
+      charset:         charset.toString()
     };
 
     sendAsyncMessage("Content:LocationChange", json);
 
     // Keep track of hash changes
     this.hashChanged = (location == this._lastLocation);
     this._lastLocation = location;
   },
--- a/mobile/chrome/content/bindings/browser.xml
+++ b/mobile/chrome/content/bindings/browser.xml
@@ -214,16 +214,17 @@
                 break;
 
               case "Content:LocationChange":
                 try {
                   let locationURI = this._browser._ios.newURI(json.location, null, null);
                   this._browser._webNavigation._currentURI = locationURI;
                   this._browser._webNavigation.canGoBack = json.canGoBack;
                   this._browser._webNavigation.canGoForward = json.canGoForward;
+                  this._browser._charset = json.charset;
                 } catch(e) {}
 
                 if (this._browser.updateWindowId(json.contentWindowId)) {
                   this._browser._documentURI = json.documentURI;
                   this._browser._searchEngines = [];
                 }
                 break;
 
@@ -717,18 +718,28 @@
                 onget="return null"
                 readonly="true"/>
 
       <property name="contentViewerFile"
                 onget="return null"
                 readonly="true"/>
 
       <property name="documentCharsetInfo"
-                onget="return null"
-                readonly="true"/>
+                readonly="true">
+         <getter><![CDATA[
+            return {
+               forcedCharset : this._charset,
+               forcedDetector : false,
+               parentCharset : "",
+               parentCharsetSource : 0
+            }
+         ]]></getter>
+      </property>
+
+      <field name="_charset"></field>
 
       <constructor>
         <![CDATA[
           this.messageManager.addMessageListener("scroll", this._messageListenerRemote);
         ]]>
       </constructor>
 
       <field name="scrollSync">true</field>
--- a/mobile/chrome/content/bindings/setting.xml
+++ b/mobile/chrome/content/bindings/setting.xml
@@ -233,16 +233,43 @@
         </body>
       </method>
 
       <property name="value" onget="return this.input.checked;" onset="return this.input.setChecked(val);"/>
       <property name="inverted" readonly="true" onget="return this.getAttribute('inverted');"/>
     </implementation>
   </binding>
 
+  <binding id="setting-localized-bool" extends="chrome://browser/content/bindings/setting.xml#setting-bool">
+    <implementation>
+      <method name="valueFromPreference">
+        <body>
+        <![CDATA[
+          let val = Services.prefs.getComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString).data;
+          if(this.inverted) val = !val;
+          this.value = (val == "true");
+         ]]>
+        </body>
+      </method>
+
+      <method name="valueToPreference">
+        <body>
+        <![CDATA[
+          let val = this.value;
+          if(this.inverted) val = !val;
+          let pref = Components.classes["@mozilla.org/pref-localizedstring;1"].createInstance(Components.interfaces.nsIPrefLocalizedString);
+          pref.data = this.inverted ? (!val).toString() : val.toString();
+          Services.prefs.setComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString, pref);
+        ]]>
+        </body>
+      </method>
+
+    </implementation>
+  </binding>
+
   <binding id="setting-integer" extends="chrome://browser/content/bindings/setting.xml#setting-base">
     <content>
       <xul:box flex="1" class="prefbox setting-integer">
         <xul:vbox flex="1">
           <xul:label class="preftitle" xbl:inherits="value=title" crop="end" flex="1"/>
           <xul:label class="prefdesc" xbl:inherits="value=desc" crop="end" flex="1">
             <children/>
           </xul:label>
--- a/mobile/chrome/content/browser-scripts.js
+++ b/mobile/chrome/content/browser-scripts.js
@@ -70,17 +70,18 @@ XPCOMUtils.defineLazyGetter(this, "Commo
 [
   ["FullScreenVideo"],
   ["BadgeHandlers"],
   ["ContextHelper"],
   ["FormHelperUI"],
   ["FindHelperUI"],
   ["NewTabPopup"],
   ["PageActions"],
-  ["BrowserSearch"]
+  ["BrowserSearch"],
+  ["CharsetMenu"]
 ].forEach(function (aObject) {
   XPCOMUtils.defineLazyGetter(window, aObject, function() {
     return CommonUI[aObject];
   });
 });
 
 /**
  * Delay load some browser scripts
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -521,16 +521,17 @@ var BrowserUI = {
 
       // Init helpers
       BadgeHandlers.register(BrowserUI._edit.popup);
       FormHelperUI.init();
       FindHelperUI.init();
       PageActions.init();
       FullScreenVideo.init();
       NewTabPopup.init();
+      CharsetMenu.init();
 
 #ifdef MOZ_UPDATER
       // Check for updates in progress
       let updatePrompt = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
       updatePrompt.checkForUpdates();
 #endif
     }, false);
 
--- a/mobile/chrome/content/browser.css
+++ b/mobile/chrome/content/browser.css
@@ -25,16 +25,20 @@ documenttab {
 settings {
   -moz-binding: url("chrome://browser/content/bindings/setting.xml#settings");
 }
 
 setting[type="bool"] {
   -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-bool");
 }
 
+setting[type="bool"][localized="true"] {
+  -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-localized-bool");
+}
+
 setting[type="boolint"] {
   -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-boolint");
 }
 
 setting[type="integer"] {
   -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-integer");
 }
 
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -338,16 +338,17 @@
 #endif
         <pageaction id="pageaction-share" title="&pageactions.share.page;"
           onclick="SharingUI.show(getBrowser().currentURI.spec, getBrowser().contentTitle);"/>
         <pageaction id="pageaction-password" title="&pageactions.password.forget;"
           onclick="PageActions.forgetPassword(event);"/>
         <pageaction id="pageaction-reset" title="&pageactions.reset;"
           onclick="PageActions.clearPagePermissions(event);"/>
         <pageaction id="pageaction-search" title="&pageactions.search.addNew;"/>
+        <pageaction id="pageaction-charset" title="&pageactions.charEncoding;" onclick="CharsetMenu.show();"/>
       </hbox>
     </arrowbox>
 
     <arrowbox id="bookmark-popup" hidden="true" align="center" offset="12">
       <label value="&bookmarkPopup.label;"/>
       <separator class="thin"/>
       <vbox>
         <button id="bookmark-popup-edit" label="&bookmarkEdit.label;" oncommand="BookmarkHelper.edit();"/>
@@ -461,16 +462,17 @@
                   <setting title="&clearPrivateData2.title;" type="control">
                     <button id="prefs-clear-data" label="&clearPrivateData.button;" command="cmd_sanitize"/>
                   </setting>
                 </settings>
                 <settings id="prefs-content" label="&content.title;">
                   <setting pref="browser.ui.zoom.reflow" title="&reflowZoom.title;" type="bool"/>
                   <setting pref="permissions.default.image" title="&showImages.title;" type="boolint" on="1" off="2"/>
                   <setting pref="javascript.enabled" type="bool" title="&enableJavaScript.title;"/>
+                  <setting pref="browser.menu.showCharacterEncoding" type="bool" localized="true" title="&showCharset.title;"/>
                 </settings>
               </richlistbox>
             </notificationbox>
           </vbox>
 
           <vbox id="console-container" flex="1">
             <vbox id="console-header">
               <label class="panel-header" value="&consoleHeader.label;"/>
--- a/mobile/chrome/content/common-ui.js
+++ b/mobile/chrome/content/common-ui.js
@@ -1220,8 +1220,93 @@ var FullScreenVideo = {
     let pos = this.browser.transformClientToBrowser(aX, aY);
     this.browser.messageManager.sendAsyncMessage(aName, {
       x: pos.x,
       y: pos.y,
       messageId: null
     });
   }
 };
+
+var CharsetMenu = {
+  _strings: null,
+  _charsets: null,
+
+  get strings() {
+    if (!this._strings)
+      this._strings = Services.strings.createBundle("chrome://global/locale/charsetTitles.properties");
+    return this._strings;
+  },
+
+  init: function() {
+    PageActions.register("pageaction-charset", this.updatePageAction, this);
+  },
+
+  updatePageAction: function(aNode) {
+    let pref = Services.prefs.getComplexValue("browser.menu.showCharacterEncoding", Ci.nsIPrefLocalizedString).data;
+    if (pref == "true") {
+      let charset = getBrowser().documentCharsetInfo.forcedCharset;
+      if (charset) {
+        charset = charset.trim().toLowerCase();
+        aNode.setAttribute("description", this.strings.GetStringFromName(charset + ".title"));
+      } else if (aNode.hasAttribute("description")) {
+        aNode.removeAttribute("description");
+      }
+    }
+    return ("true" == pref)
+  },
+
+  _toMenuItems: function(aCharsets, aCurrent) {
+    let ret = [];
+    aCharsets.forEach(function (aSet) {
+      try {
+        let string = aSet.trim().toLowerCase();
+        ret.push({
+          label: this.strings.GetStringFromName(string + ".title"),
+          value: string,
+          selected: (string == aCurrent)
+        });
+      } catch(ex) { }
+    }, this);
+    return ret;
+  },
+
+  menu : {
+    dispatchEvent: function(aEvent) {
+      if (aEvent.type == "command")
+        CharsetMenu.setCharset(this.menupopup.children[this.selectedIndex].value);
+    },
+    menupopup: {
+      hasAttribute: function(aAttr) { return false; },
+    },
+    selectedIndex: -1
+  },
+
+  get charsets() {
+    if (!this._charsets) {
+      this._charsets = Services.prefs.getComplexValue("intl.charsetmenu.browser.static", Ci.nsIPrefLocalizedString).data.split(",");
+    }
+    let charsets = this._charsets;
+    let currentCharset = getBrowser().documentCharsetInfo.forcedCharset;
+    
+    if (currentCharset) {
+      currentCharset = currentCharset.trim().toLowerCase();
+      if (charsets.indexOf(currentCharset) == -1)
+        charsets.splice(0, 0, currentCharset);
+    }
+    return this._toMenuItems(charsets, currentCharset);
+  },
+
+  show: function showCharsetMenu() {
+    this.menu.menupopup.children = this.charsets;
+    MenuListHelperUI.show(this.menu);
+  },
+
+  setCharset: function setCharset(aCharset) {
+    let browser = getBrowser();
+    browser.messageManager.sendAsyncMessage("Browser:SetCharset", {
+      charset: aCharset
+    });
+    let history = Cc["@mozilla.org/browser/nav-history-service;1"].getService(Ci.nsINavHistoryService);
+    history.setCharsetForURI(browser.documentURI, aCharset);
+  }
+
+};
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -261,16 +261,17 @@ let Content = {
     addMessageListener("Browser:MouseOver", this);
     addMessageListener("Browser:MouseLong", this);
     addMessageListener("Browser:MouseDown", this);
     addMessageListener("Browser:MouseUp", this);
     addMessageListener("Browser:MouseCancel", this);
     addMessageListener("Browser:SaveAs", this);
     addMessageListener("Browser:ZoomToPoint", this);
     addMessageListener("Browser:MozApplicationCache:Fetch", this);
+    addMessageListener("Browser:SetCharset", this);
 
     if (Util.isParentProcess())
       addEventListener("DOMActivate", this, true);
 
     addEventListener("MozApplicationManifest", this, false);
     addEventListener("command", this, false);
     addEventListener("pagehide", this, false);
     addEventListener("keypress", this, false, false);
@@ -519,16 +520,25 @@ let Content = {
       case "Browser:MozApplicationCache:Fetch": {
         let currentURI = Services.io.newURI(json.location, json.charset, null);
         let manifestURI = Services.io.newURI(json.manifest, json.charset, currentURI);
         let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
                             .getService(Ci.nsIOfflineCacheUpdateService);
         updateService.scheduleUpdate(manifestURI, currentURI, content);
         break;
       }
+
+      case "Browser:SetCharset": {
+        let docCharset = docShell.QueryInterface(Ci.nsIDocCharset);
+        docCharset.charset = json.charset;
+    
+        let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+        webNav.reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+        break;
+      }
     }
   },
 
   _doTapHighlight: function _doTapHighlight(aElt) {
     gDOMUtils.setContentState(aElt, kStateActive);
   },
 
   _cancelTapHighlight: function _cancelTapHighlight() {
--- a/mobile/locales/en-US/chrome/browser.dtd
+++ b/mobile/locales/en-US/chrome/browser.dtd
@@ -104,10 +104,11 @@
 <!ENTITY contextRemoveBookmark.label  "Remove">
 
 <!ENTITY pageactions.saveas.pdf      "Save As PDF">
 <!ENTITY pageactions.share.page      "Share Page">
 <!ENTITY pageactions.password.forget "Forget Password">
 <!ENTITY pageactions.reset           "Clear Site Preferences">
 <!ENTITY pageactions.findInPage      "Find In Page">
 <!ENTITY pageactions.search.addNew   "Add Search Engine">
+<!ENTITY pageactions.charEncoding    "Character Encoding">
 
 <!ENTITY appMenu.siteOptions         "Site Options">
--- a/mobile/locales/en-US/chrome/browser.properties
+++ b/mobile/locales/en-US/chrome/browser.properties
@@ -205,8 +205,20 @@ opensearch.searchFor=Search for "%S"
 # LOCALIZATION NOTE: openinapp.specific is the text displayed if there is a single external app
 # %S is the name of the app, like "YouTube" or "Picassa"
 openinapp.specific=Open in %S App
 openinapp.general=Open in Another App
 
 # Clear Private Data
 clearPrivateData.title=Clear Private Data
 clearPrivateData.message=Delete your browsing history and settings, including passwords and cookies?
+
+# LOCALIZATION NOTE (browser.menu.showCharacterEncoding): Set to the string
+# "true" (spelled and capitalized exactly that way) to show the "Character
+# Encoding" menu in the site menu. Any other value will hide it. Without this
+# setting, the "Character Encoding" menu must be enabled via Preferences.
+# This is not a string to translate. If users frequently use the "Character Encoding"
+# menu, set this to "true". Otherwise, you can leave it as "false".
+browser.menu.showCharacterEncoding=false
+
+# LOCALIZATION NOTE (intl.charsetmenu.browser.static): Set to a series of comma separated
+# values for charsets that the user can select from in the Character Encoding menu.
+intl.charsetmenu.browser.static=iso-8859-1,utf-8,x-gbk,big5,iso-2022-jp,shift_jis,euc-jp
--- a/mobile/locales/en-US/chrome/preferences.dtd
+++ b/mobile/locales/en-US/chrome/preferences.dtd
@@ -14,8 +14,9 @@
 <!ENTITY language.title                            "Language">
 <!ENTITY language.auto                             "Auto-detect">
 <!ENTITY defaultBrowser.title                      "Default Browser">
 <!ENTITY defaultBrowser.description                "Make &brandShortName; your default browser">
 <!ENTITY homepage.title                            "Start page">
 <!ENTITY homepage.none                             "Blank Page">
 <!ENTITY homepage.default                          "&brandShortName; Start">
 <!ENTITY homepage.currentpage                      "Use Current Page">
+<!ENTITY showCharset.title                         "Show Character Encoding">