[GFX] Add hostname elider. Add ability to elide strings at the beginning.

[email protected]

Review URL: https://codereview.chromium.org/144513002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248726 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/ui/elide_url.cc b/chrome/browser/ui/elide_url.cc
index 1ce9ce571..ba48aae64 100644
--- a/chrome/browser/ui/elide_url.cc
+++ b/chrome/browser/ui/elide_url.cc
@@ -21,6 +21,8 @@
 
 namespace {
 
+const base::char16 kDot = '.';
+
 // Build a path from the first |num_components| elements in |path_elements|.
 // Prepends |path_prefix|, appends |filename|, inserts ellipsis if appropriate.
 base::string16 BuildPathFromComponents(
@@ -67,6 +69,39 @@
   return base::string16();
 }
 
+// Splits the hostname in the |url| into sub-strings for the full hostname,
+// the domain (TLD+1), and the subdomain (everything leading the domain).
+void SplitHost(const GURL& url,
+               base::string16* url_host,
+               base::string16* url_domain,
+               base::string16* url_subdomain) {
+  // Get Host.
+  *url_host = UTF8ToUTF16(url.host());
+
+  // Get domain and registry information from the URL.
+  *url_domain = UTF8ToUTF16(
+      net::registry_controlled_domains::GetDomainAndRegistry(
+          url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES));
+  if (url_domain->empty())
+    *url_domain = *url_host;
+
+  // Add port if required.
+  if (!url.port().empty()) {
+    *url_host += UTF8ToUTF16(":" + url.port());
+    *url_domain += UTF8ToUTF16(":" + url.port());
+  }
+
+  // Get sub domain.
+  const size_t domain_start_index = url_host->find(*url_domain);
+  base::string16 kWwwPrefix = UTF8ToUTF16("www.");
+  if (domain_start_index != base::string16::npos)
+    *url_subdomain = url_host->substr(0, domain_start_index);
+  if ((*url_subdomain == kWwwPrefix || url_subdomain->empty() ||
+      url.SchemeIsFile())) {
+    url_subdomain->clear();
+  }
+}
+
 }  // namespace
 
 // TODO(pkasting): http://crbug.com/77883 This whole function gets
@@ -109,32 +144,10 @@
     return ElideText(url_string, font_list, available_pixel_width,
                      gfx::ELIDE_AT_END);
 
-  // Get Host.
-  base::string16 url_host = UTF8ToUTF16(url.host());
-
-  // Get domain and registry information from the URL.
-  base::string16 url_domain = UTF8ToUTF16(
-      net::registry_controlled_domains::GetDomainAndRegistry(
-          url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES));
-  if (url_domain.empty())
-    url_domain = url_host;
-
-  // Add port if required.
-  if (!url.port().empty()) {
-    url_host += UTF8ToUTF16(":" + url.port());
-    url_domain += UTF8ToUTF16(":" + url.port());
-  }
-
-  // Get sub domain.
+  base::string16 url_host;
+  base::string16 url_domain;
   base::string16 url_subdomain;
-  const size_t domain_start_index = url_host.find(url_domain);
-  if (domain_start_index != base::string16::npos)
-    url_subdomain = url_host.substr(0, domain_start_index);
-  const base::string16 kWwwPrefix = UTF8ToUTF16("www.");
-  if ((url_subdomain == kWwwPrefix || url_subdomain.empty() ||
-      url.SchemeIsFile())) {
-    url_subdomain.clear();
-  }
+  SplitHost(url, &url_host, &url_domain, &url_subdomain);
 
   // If this is a file type, the path is now defined as everything after ":".
   // For example, "C:/aa/aa/bb", the path is "/aa/bb/cc". Interesting, the
@@ -265,3 +278,27 @@
                    gfx::ELIDE_AT_END);
 }
 
+base::string16 ElideHost(const GURL& url,
+                         const gfx::FontList& font_list,
+                         float available_pixel_width) {
+  base::string16 url_host;
+  base::string16 url_domain;
+  base::string16 url_subdomain;
+  SplitHost(url, &url_host, &url_domain, &url_subdomain);
+
+  const float pixel_width_url_host = GetStringWidthF(url_host, font_list);
+  if (available_pixel_width >= pixel_width_url_host)
+    return url_host;
+
+  if (url_subdomain.empty())
+    return url_domain;
+
+  const float pixel_width_url_domain = GetStringWidthF(url_domain, font_list);
+  float subdomain_width = available_pixel_width - pixel_width_url_domain;
+  if (subdomain_width <= 0)
+    return base::string16(kEllipsisUTF16) + kDot + url_domain;
+
+  base::string16 elided_subdomain = ElideText(
+      url_subdomain, font_list, subdomain_width, gfx::ELIDE_AT_BEGINNING);
+  return elided_subdomain + url_domain;
+}
diff --git a/chrome/browser/ui/elide_url.h b/chrome/browser/ui/elide_url.h
index 4842281b..3bf6263f 100644
--- a/chrome/browser/ui/elide_url.h
+++ b/chrome/browser/ui/elide_url.h
@@ -36,4 +36,13 @@
                         float available_pixel_width,
                         const std::string& languages);
 
+// This function takes a GURL object and elides the host to fit within
+// the given width. The function will never elide past the TLD+1 point,
+// but after that, will leading-elide the domain name to fit the width.
+// Example: http://sub.domain.com ---> "...domain.com", or "s...domain.com"
+// depending on the width.
+base::string16 ElideHost(const GURL& host_url,
+                         const gfx::FontList& font_list,
+                         float available_pixel_width);
+
 #endif  // CHROME_BROWSER_UI_ELIDE_URL_H_
diff --git a/chrome/browser/ui/tests/elide_url_unittest.cc b/chrome/browser/ui/tests/elide_url_unittest.cc
index 8d32109..56826ead 100644
--- a/chrome/browser/ui/tests/elide_url_unittest.cc
+++ b/chrome/browser/ui/tests/elide_url_unittest.cc
@@ -161,4 +161,41 @@
   RunUrlTest(testcases, arraysize(testcases));
 }
 
+TEST(TextEliderTest, TestHostEliding) {
+  const std::string kEllipsisStr(kEllipsis);
+  Testcase testcases[] = {
+    {"http://google.com", "google.com"},
+    {"http://subdomain.google.com", kEllipsisStr + ".google.com"},
+    {"http://reallyreallyreallylongdomainname.com",
+         "reallyreallyreallylongdomainname.com"},
+    {"http://a.b.c.d.e.f.com", kEllipsisStr + "f.com"},
+    {"http://foo", "foo"},
+    {"http://foo.bar", "foo.bar"},
+    {"http://subdomain.foo.bar", kEllipsisStr + "in.foo.bar"},
+// IOS width calculations are off by a letter from other platforms for
+// some strings from other platforms, probably for strings with too
+// many kerned letters on the default font set.
+#if !defined(OS_IOS)
+    {"http://subdomain.reallylongdomainname.com",
+         kEllipsisStr + "ain.reallylongdomainname.com"},
+    {"http://a.b.c.d.e.f.com", kEllipsisStr + ".e.f.com"},
+#endif
+  };
+
+  for (size_t i = 0; i < arraysize(testcases); ++i) {
+    const float available_width =
+        GetStringWidthF(UTF8ToUTF16(testcases[i].output), gfx::FontList());
+    EXPECT_EQ(UTF8ToUTF16(testcases[i].output), ElideHost(
+        GURL(testcases[i].input), gfx::FontList(), available_width));
+  }
+
+  // Trying to elide to a really short length will still keep the full TLD+1
+  EXPECT_EQ(base::ASCIIToUTF16("google.com"),
+            ElideHost(GURL("http://google.com"), gfx::FontList(), 2));
+  EXPECT_EQ(base::UTF8ToUTF16(kEllipsisStr + ".google.com"),
+            ElideHost(GURL("http://subdomain.google.com"), gfx::FontList(), 2));
+  EXPECT_EQ(base::ASCIIToUTF16("foo.bar"),
+            ElideHost(GURL("http://foo.bar"), gfx::FontList(), 2));
+}
+
 }  // namespace