Bug 1448553 - Part 3: Decodeds Punycode-encoded international domain names in the Storage(developer tool) so that they are displayed as human-readable Unicode text. r?ntim draft
authorZhang Junzhi <zjz@zjz.name>
Mon, 02 Apr 2018 02:16:32 +0800
changeset 775901 ffa7ad62d2612bf68627a3d65e6fe0b4cfd8526c
parent 775477 231f533d6ce7f5c312aae53505ba7841cd775b7d
push id104748
push userbmo:zjz@zjz.name
push dateSun, 01 Apr 2018 18:39:17 +0000
reviewersntim
bugs1448553
milestone61.0a1
Bug 1448553 - Part 3: Decodeds Punycode-encoded international domain names in the Storage(developer tool) so that they are displayed as human-readable Unicode text. r?ntim The Punycode-encoded international domain names are human-unreadable, so they should be displayed as human-readable Unicode text. This commit decodes this kind of names in the Storage(developer tool). MozReview-Commit-ID: 6Kik0JoGiZv
devtools/client/storage/ui.js
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -1,15 +1,16 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 "use strict";
 
+const { Cc, Ci } = require("chrome");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const JSOL = require("devtools/client/shared/vendor/jsol");
 const {KeyCodes} = require("devtools/client/shared/keycodes");
 
 // GUID to be used as a separator in compound keys. This must match the same
 // constant in devtools/server/actors/storage.js,
@@ -426,19 +427,25 @@ class StorageUI {
   }
 
   /**
    * Handle added items received by onEdit
    *
    * @param {object} See onEdit docs
    */
   async handleAddedItems(added) {
+    // idnService is used to convert host names into readable Unicode domain
+    // names if they are in Punycode.
+    const idnService =
+            Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
+
     for (let type in added) {
       for (let host in added[type]) {
-        this.tree.add([type, {id: host, type: "url"}]);
+        const label = this.getReadableLabelFromHostname(host, idnService);
+        this.tree.add([type, {id: host, label: label, type: "url"}]);
         for (let name of added[type][host]) {
           try {
             name = JSON.parse(name);
             if (name.length == 3) {
               name.splice(2, 1);
             }
             this.tree.add([type, host, ...name]);
             if (!this.tree.selectedItem) {
@@ -626,16 +633,21 @@ class StorageUI {
    * Populates the storage tree which displays the list of storages present for
    * the page.
    *
    * @param {object} storageTypes
    *        List of storages and their corresponding hosts returned by the
    *        StorageFront.listStores call.
    */
   populateStorageTree(storageTypes) {
+    // idnService is used to convert host names into readable Unicode domain
+    // names if they are in Punycode.
+    const idnService =
+            Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
+
     this.storageTypes = {};
     for (let type in storageTypes) {
       // Ignore `from` field, which is just a protocol.js implementation
       // artifact.
       if (type === "from") {
         continue;
       }
       let typeLabel = type;
@@ -645,17 +657,18 @@ class StorageUI {
         console.error("Unable to localize tree label type:" + type);
       }
       this.tree.add([{id: type, label: typeLabel, type: "store"}]);
       if (!storageTypes[type].hosts) {
         continue;
       }
       this.storageTypes[type] = storageTypes[type];
       for (let host in storageTypes[type].hosts) {
-        this.tree.add([type, {id: host, type: "url"}]);
+        const label = this.getReadableLabelFromHostname(host, idnService);
+        this.tree.add([type, {id: host, label: label, type: "url"}]);
         for (let name of storageTypes[type].hosts[host]) {
           try {
             let names = JSON.parse(name);
             this.tree.add([type, host, ...names]);
             if (!this.tree.selectedItem) {
               this.tree.selectedItem = [type, host, names[0], names[1]];
             }
           } catch (ex) {
@@ -746,23 +759,55 @@ class StorageUI {
         this.parseItemValue(key, item[key]);
       }
     }
 
     this.emit("sidebar-updated");
   }
 
   /**
+   * Gets a readable label from the hostname. If the hostname is a Punycode
+   * domain(I.e. a ASCII domain name representing a Unicode domain name), then
+   * this function decodes it to the readable Unicode domain name, and label
+   * the Unicode domain name toggether with the original domian name, and then
+   * return the label; if the hostname isn't a Punycode domain(I.e. it isn't
+   * encoded and is readable on its own), then this function simply returns the
+   * original hostname.
+   *
+   * @param {string} host
+   *        The string representing a host, e.g, example.com, example.com:8000
+   * @param {object} idnService
+   *        The Idn Service instance(Idn stands for International Domain Name)
+   *        used to decode a Punycode domain name into a Unicode domain name
+   */
+  getReadableLabelFromHostname(host, idnService) {
+    try {
+      const hostname = new URL(host).hostname;
+      const readableHostname = idnService.convertToDisplayIDN(hostname, {});
+      if (hostname !== readableHostname) {
+        // If the hostname is a Punycode domain representing a Unicode domain,
+        // we decode it to the Unicode domain name, and then label the Unicode
+        // domain name together with the original domain name.
+        return host.replace(hostname, readableHostname) + " [ " + host + " ]";
+      }
+    } catch (_) {
+      // Skip decoding for a host which doesn't include a domain name, simply
+      // consider them to be readable.
+    }
+    return host;
+  }
+
+  /**
    * Tries to parse a string value into either a json or a key-value separated
    * object and populates the sidebar with the parsed value. The value can also
    * be a key separated array.
    *
    * @param {string} name
    *        The key corresponding to the `value` string in the object
-   * @param {string} value
+   * @param {string} originalValue
    *        The string to be parsed into an object
    */
   parseItemValue(name, originalValue) {
     // Find if value is URLEncoded ie
     let decodedValue = "";
     try {
       decodedValue = decodeURIComponent(originalValue);
     } catch (e) {
@@ -854,18 +899,16 @@ class StorageUI {
     }
     return null;
   }
 
   /**
    * Select handler for the storage tree. Fetches details of the selected item
    * from the storage details and populates the storage tree.
    *
-   * @param {string} event
-   *        The name of the event fired
    * @param {array} item
    *        An array of ids which represent the location of the selected item in
    *        the storage tree
    */
   async onHostSelect(item) {
     this.table.clear();
     this.hideSidebar();
     this.searchBox.value = "";