--- 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 = "";