Bug 1448553 - Part 1: Decodeds Punycode-encoded international domain names and URI-encoded filenames in the Web console(developer tool) so that they are displayed as human-readable Unicode text. r?jsantell draft
authorZhang Junzhi <zjz@zjz.name>
Sun, 01 Apr 2018 14:39:57 +0800
changeset 775476 a3e9cc05b99b6fd6593ed93b012b79b45086ca91
parent 775474 3f9a70b125f6fb9be2b209159657fd7ae5515c01
child 775477 231f533d6ce7f5c312aae53505ba7841cd775b7d
push id104737
push userbmo:zjz@zjz.name
push dateSun, 01 Apr 2018 06:58:08 +0000
reviewersjsantell
bugs1448553
milestone61.0a1
Bug 1448553 - Part 1: Decodeds Punycode-encoded international domain names and URI-encoded filenames in the Web console(developer tool) so that they are displayed as human-readable Unicode text. r?jsantell The Punycode-encoded international domain names and URI-encoded filenames are human-unreadable, so they should be displayed as human-readable Unicode text. This commit decodes this kind of names in the Web console. MozReview-Commit-ID: Jev7OlF0U9I
devtools/client/shared/components/Frame.js
devtools/client/shared/source-utils.js
--- a/devtools/client/shared/components/Frame.js
+++ b/devtools/client/shared/components/Frame.js
@@ -127,29 +127,30 @@ class Frame extends Component {
     // resource://devtools/shared/base-loader.js -> resource://devtools/path/to/file.js .
     // What's needed is only the last part after " -> ".
     let source = frame.source
       ? String(frame.source).split(" -> ").pop()
       : "";
     let line = frame.line != void 0 ? Number(frame.line) : null;
     let column = frame.column != void 0 ? Number(frame.column) : null;
 
-    const { short, long, host } = getSourceNames(source);
+    const { short, long, host, readableShort, readableLong, readableHost } =
+            getSourceNames(source);
     // Reparse the URL to determine if we should link this; `getSourceNames`
     // has already cached this indirectly. We don't want to attempt to
     // link to "self-hosted" and "(unknown)". However, we do want to link
     // to Scratchpad URIs.
     // Source mapped sources might not necessary linkable, but they
     // are still valid in the debugger.
     const isLinkable = !!(isScratchpadScheme(source) || parseURL(source))
       || isSourceMapped;
     const elements = [];
     const sourceElements = [];
     let sourceEl;
-    let tooltip = long;
+    let tooltip = readableLong ? readableLong : long;
 
     // Exclude all falsy values, including `0`, as line numbers start with 1.
     if (line) {
       tooltip += `:${line}`;
       // Intentionally exclude 0
       if (column) {
         tooltip += `:${column}`;
       }
@@ -172,17 +173,22 @@ class Frame extends Component {
             key: "function-display-name",
             className: "frame-link-function-display-name",
           }, functionDisplayName),
           " "
         );
       }
     }
 
-    let displaySource = showFullSourceUrl ? long : short;
+    let displaySource;
+    if (showFullSourceUrl) {
+      displaySource = readableLong ? readableLong : long;
+    } else {
+      displaySource = readableShort ? readableShort : short;
+    }
     if (isSourceMapped) {
       displaySource = getSourceMappedFile(displaySource);
     } else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
       displaySource = host;
     }
 
     sourceElements.push(dom.span({
       key: "filename",
@@ -233,21 +239,21 @@ class Frame extends Component {
     } else {
       sourceEl = dom.span({
         key: "source",
         className: "frame-link-source",
       }, sourceInnerEl);
     }
     elements.push(sourceEl);
 
-    if (showHost && host) {
+    if (showHost && host || readableHost) {
       elements.push(" ");
       elements.push(dom.span({
         key: "host",
         className: "frame-link-host",
-      }, host));
+      }, readableHost ? readableHost : host));
     }
 
     return dom.span(attributes, ...elements);
   }
 }
 
 module.exports = Frame;
--- a/devtools/client/shared/source-utils.js
+++ b/devtools/client/shared/source-utils.js
@@ -1,13 +1,15 @@
 /* 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 { LocalizationHelper } = require("devtools/shared/l10n");
 
 const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
 const UNKNOWN_SOURCE_STRING = l10n.getStr("frame.unknownSource");
 
 // Character codes used in various parsing helper functions.
 const CHAR_CODE_A = "a".charCodeAt(0);
 const CHAR_CODE_B = "b".charCodeAt(0);
@@ -116,17 +118,34 @@ function parseURL(location) {
  */
 function getSourceNames(source) {
   let data = gSourceNamesStore.get(source);
 
   if (data) {
     return data;
   }
 
+  // The difference between values with or without readable is that the values
+  // without readable may be sometimes human-unreadable, in this case, the
+  // corresponding ones with readable will be in a converted format which is
+  // human-readable.
+  //
+  // For example, when denoting a Javascript filename with non-ASCII
+  // characters, the `short` would be something like %E6%B8%AC.js whereas the
+  // |readableShort| would be the string in a human-readble format converted by
+  // decodeURIComponent; or for example, when denoting a Unicode domain name,
+  // the `host` would be something like xn--g6w.xn--8pv in Punycode whereas the
+  // `readableHost` would be the human-readble string decoded from Punycode.
+  //
+  // If a value without readable is human-readable on its own, then the value of
+  // the corresponding one with readable will be "undefined"(primitive value,
+  // not adjective).
   let short, long, host;
+  let readableShort, readableLong, readableHost;
+
   const sourceStr = source ? String(source) : "";
 
   // If `data:...` uri
   if (isDataScheme(sourceStr)) {
     let commaIndex = sourceStr.indexOf(",");
     if (commaIndex > -1) {
       // The `short` name for a data URI becomes `data:` followed by the actual
       // encoded content, omitting the MIME type, and charset.
@@ -164,26 +183,41 @@ function getSourceNames(source) {
 
     short = parsedUrl.fileName;
     // If `short` is just a slash, and we actually have a path,
     // strip the slash and parse again to get a more useful short name.
     // e.g. "http://foo.com/bar/" -> "bar", rather than "/"
     if (short === "/" && parsedUrl.pathname !== "/") {
       short = parseURL(long.replace(/\/$/, "")).fileName;
     }
+
+    // Get the readable names.
+    const idnService =
+            Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
+    readableHost = idnService.convertToDisplayIDN(host, {});
+    try {
+      readableLong = decodeURIComponent(long.replace(host, readableHost));
+    } catch (_) {
+      // Skip decoding if the URI is malformed.
+    }
+    try {
+      readableShort = decodeURIComponent(short);
+    } catch (_) {
+      // Skip decoding if the URI is malformed.
+    }
   }
 
   if (!short) {
     if (!long) {
       long = UNKNOWN_SOURCE_STRING;
     }
     short = long.slice(0, 100);
   }
 
-  let result = { short, long, host };
+  let result = { short, long, host, readableShort, readableLong, readableHost };
   gSourceNamesStore.set(source, result);
   return result;
 }
 
 // For the functions below, we assume that we will never access the location
 // argument out of bounds, which is indeed the vast majority of cases.
 //
 // They are written this way because they are hot. Each frame is checked for