Allow zooming error pages.

This CL operates by providing a mechanism in HostZoomMap to convert urls
into the error-page url when an error page is loaded. The error-page
url is used to track the zoom-levels for error pages. Callers to
HostZoomMap::SetZoomLevelForHost*() functions are expected to convert
URLs obtained from NavigationEntrys before setting zoom levels.

HostZoomMap is also modified to add a function to transmit the zoom
level for a newly loaded error page, since it doesn't follow the usual
loading pathway. The transmission of the zoom level is triggered
when an error page is detected in
NavigationControllerImpl::RendererDidNavigateToNewPage().

The CL also creates a localization string to represent error pages in
the Content Settings page list of hosts with non-default zooms, this
since the internal url representations for error pages are not suitable
to be displayed to a user.

BUG=417699

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

Cr-Commit-Position: refs/heads/master@{#301707}
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index a179f79..9085bdb1 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8075,6 +8075,9 @@
       <message name="IDS_ZOOMLEVELS_HEADER_AND_TAB_LABEL" desc="Label for Zoom Level tab and a header the on Content Settings dialog (zoom levels indicate that a page should be shown for example at 110% magnification).">
         Zoom Levels
       </message>
+      <message name="IDS_ZOOMLEVELS_CHROME_ERROR_PAGES_LABEL" desc="Label for zoom level list entry for Chrome error pages.">
+        (Chrome error pages)
+      </message>
       <message name="IDS_ZOOMLEVELS_MANAGE_BUTTON" desc="Button on the Content Settings dialog that opens a dialog to manage your page zoom levels (zoom levels indicate that a page should be shown for example at 110% magnification).">
         Manage...
       </message>
diff --git a/chrome/browser/ui/webui/options/content_settings_handler.cc b/chrome/browser/ui/webui/options/content_settings_handler.cc
index 129bc4e..b4ef36bc 100644
--- a/chrome/browser/ui/webui/options/content_settings_handler.cc
+++ b/chrome/browser/ui/webui/options/content_settings_handler.cc
@@ -44,6 +44,7 @@
 #include "content/public/browser/web_ui.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/page_zoom.h"
+#include "content/public/common/url_constants.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/permissions/api_permission.h"
@@ -996,9 +997,15 @@
        ++i) {
     scoped_ptr<base::DictionaryValue> exception(new base::DictionaryValue);
     switch (i->mode) {
-      case content::HostZoomMap::ZOOM_CHANGED_FOR_HOST:
+      case content::HostZoomMap::ZOOM_CHANGED_FOR_HOST: {
         exception->SetString(kOrigin, i->host);
-        break;
+        std::string host = i->host;
+        if (host == content::kUnreachableWebDataURL) {
+          host =
+              l10n_util::GetStringUTF8(IDS_ZOOMLEVELS_CHROME_ERROR_PAGES_LABEL);
+        }
+        exception->SetString(kOrigin, host);
+      }
       case content::HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST:
         // These are not stored in preferences and get cleared on next browser
         // start. Therefore, we don't care for them.
@@ -1219,6 +1226,11 @@
   rv = args->GetString(2, &pattern);
   DCHECK(rv);
 
+  if (pattern ==
+          l10n_util::GetStringUTF8(IDS_ZOOMLEVELS_CHROME_ERROR_PAGES_LABEL)) {
+    pattern = content::kUnreachableWebDataURL;
+  }
+
   content::HostZoomMap* host_zoom_map =
       content::HostZoomMap::GetDefaultForBrowserContext(
           GetBrowserContext(web_ui()));
diff --git a/chrome/browser/ui/zoom/zoom_controller.cc b/chrome/browser/ui/zoom/zoom_controller.cc
index 50fb6946..a9b7029 100644
--- a/chrome/browser/ui/zoom/zoom_controller.cc
+++ b/chrome/browser/ui/zoom/zoom_controller.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/zoom/zoom_event_manager.h"
 #include "chrome/browser/ui/zoom/zoom_observer.h"
 #include "content/public/browser/host_zoom_map.h"
+#include "content/public/browser/navigation_details.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -81,13 +82,10 @@
     const scoped_refptr<const extensions::Extension>& extension) {
   content::NavigationEntry* entry =
       web_contents()->GetController().GetLastCommittedEntry();
-  bool is_normal_page =
-      entry && entry->GetPageType() == content::PAGE_TYPE_NORMAL;
   // Cannot zoom in disabled mode. Also, don't allow changing zoom level on
   // a crashed tab, an error page or an interstitial page.
   if (zoom_mode_ == ZOOM_MODE_DISABLED ||
-      !web_contents()->GetRenderViewHost()->IsRenderViewLive() ||
-      !is_normal_page)
+      !web_contents()->GetRenderViewHost()->IsRenderViewLive())
     return false;
 
   // Store extension data so that |extension| can be attributed when the zoom
@@ -139,7 +137,8 @@
       last_extension_ = NULL;
       return false;
     }
-    std::string host = net::GetHostOrSpecFromURL(entry->GetURL());
+    std::string host =
+        net::GetHostOrSpecFromURL(content::HostZoomMap::GetURLFromEntry(entry));
     zoom_map->SetZoomLevelForHost(host, zoom_level);
   }
 
@@ -172,7 +171,7 @@
           web_contents()->GetController().GetLastCommittedEntry();
 
       if (entry) {
-        GURL url = entry->GetURL();
+        GURL url = content::HostZoomMap::GetURLFromEntry(entry);
         std::string host = net::GetHostOrSpecFromURL(url);
 
         if (zoom_map->HasZoomLevel(url.scheme(), host)) {
@@ -245,6 +244,9 @@
 void ZoomController::DidNavigateMainFrame(
     const content::LoadCommittedDetails& details,
     const content::FrameNavigateParams& params) {
+  if (details.entry && details.entry->GetPageType() == content::PAGE_TYPE_ERROR)
+    content::HostZoomMap::SendErrorPageZoomLevelRefresh(web_contents());
+
   // If the main frame's content has changed, the new page may have a different
   // zoom level from the old one.
   UpdateState(std::string());
@@ -269,7 +271,8 @@
     content::NavigationEntry* entry =
         web_contents()->GetController().GetLastCommittedEntry();
     if (!entry ||
-        host != net::GetHostOrSpecFromURL(entry->GetURL())) {
+        host != net::GetHostOrSpecFromURL(
+                    content::HostZoomMap::GetURLFromEntry(entry))) {
       return;
     }
   }
diff --git a/chrome/browser/ui/zoom/zoom_controller_browsertest.cc b/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
index c43b99a1..36a211489 100644
--- a/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
+++ b/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
@@ -114,7 +114,7 @@
   zoom_change_watcher.Wait();
 }
 
-IN_PROC_BROWSER_TEST_F(ZoomControllerBrowserTest, ErrorPagesDoNotZoom) {
+IN_PROC_BROWSER_TEST_F(ZoomControllerBrowserTest, ErrorPagesCanZoom) {
   ui_test_utils::NavigateToURL(browser(), GURL("http://kjfhkjsdf.com"));
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -131,5 +131,5 @@
   // The following attempt to change the zoom level for an error page should
   // fail.
   zoom_controller->SetZoomLevel(new_zoom_level);
-  EXPECT_FLOAT_EQ(old_zoom_level, zoom_controller->GetZoomLevel());
+  EXPECT_FLOAT_EQ(new_zoom_level, zoom_controller->GetZoomLevel());
 }
diff --git a/content/browser/host_zoom_map_impl.cc b/content/browser/host_zoom_map_impl.cc
index 9c595ef..81f8182 100644
--- a/content/browser/host_zoom_map_impl.cc
+++ b/content/browser/host_zoom_map_impl.cc
@@ -21,6 +21,7 @@
 #include "content/public/browser/notification_types.h"
 #include "content/public/browser/resource_context.h"
 #include "content/public/common/page_zoom.h"
+#include "content/public/common/url_constants.h"
 #include "net/base/net_util.h"
 
 namespace content {
@@ -43,11 +44,22 @@
   if (!entry)
     return std::string();
 
-  return net::GetHostOrSpecFromURL(entry->GetURL());
+  return net::GetHostOrSpecFromURL(HostZoomMap::GetURLFromEntry(entry));
 }
 
 }  // namespace
 
+GURL HostZoomMap::GetURLFromEntry(const NavigationEntry* entry) {
+  switch (entry->GetPageType()) {
+    case PAGE_TYPE_ERROR:
+      return GURL(kUnreachableWebDataURL);
+    // TODO(wjmaclean): In future, give interstitial pages special treatment as
+    // well.
+    default:
+      return entry->GetURL();
+  }
+}
+
 HostZoomMap* HostZoomMap::GetDefaultForBrowserContext(BrowserContext* context) {
   HostZoomMapImpl* rv = static_cast<HostZoomMapImpl*>(
       context->GetUserData(kHostZoomMapKeyName));
@@ -76,6 +88,14 @@
       *static_cast<const WebContentsImpl*>(web_contents), level);
 }
 
+void HostZoomMap::SendErrorPageZoomLevelRefresh(
+    const WebContents* web_contents) {
+  HostZoomMapImpl* host_zoom_map =
+      static_cast<HostZoomMapImpl*>(HostZoomMap::GetDefaultForBrowserContext(
+          web_contents->GetBrowserContext()));
+  host_zoom_map->SendErrorPageZoomLevelRefresh();
+}
+
 HostZoomMapImpl::HostZoomMapImpl()
     : default_zoom_level_(0.0) {
   registrar_.Add(
@@ -253,7 +273,7 @@
   // It is possible for a WebContent's zoom level to be queried before
   // a navigation has occurred.
   if (entry)
-    url = entry->GetURL();
+    url = GetURLFromEntry(entry);
   return GetZoomLevelForHostAndScheme(url.scheme(),
                                       net::GetHostOrSpecFromURL(url));
 }
@@ -277,7 +297,7 @@
     if (!entry)
       return;
 
-    GURL url = entry->GetURL();
+    GURL url = GetURLFromEntry(entry);
     SetZoomLevelForHost(net::GetHostOrSpecFromURL(url), level);
   }
 }
@@ -384,6 +404,14 @@
   }
 }
 
+void HostZoomMapImpl::SendErrorPageZoomLevelRefresh() {
+  GURL error_url(kUnreachableWebDataURL);
+  std::string host = net::GetHostOrSpecFromURL(error_url);
+  double error_page_zoom_level = GetZoomLevelForHost(host);
+
+  SendZoomLevelChange(std::string(), host, error_page_zoom_level);
+}
+
 HostZoomMapImpl::~HostZoomMapImpl() {
 }
 
diff --git a/content/browser/host_zoom_map_impl.h b/content/browser/host_zoom_map_impl.h
index d2a8a67c..78d643d 100644
--- a/content/browser/host_zoom_map_impl.h
+++ b/content/browser/host_zoom_map_impl.h
@@ -86,6 +86,8 @@
                const NotificationSource& source,
                const NotificationDetails& details) override;
 
+  void SendErrorPageZoomLevelRefresh();
+
  private:
   typedef std::map<std::string, double> HostZoomLevels;
   typedef std::map<std::string, HostZoomLevels> SchemeHostZoomLevels;
diff --git a/content/public/browser/host_zoom_map.h b/content/public/browser/host_zoom_map.h
index 1632690f..e9e523e 100644
--- a/content/public/browser/host_zoom_map.h
+++ b/content/public/browser/host_zoom_map.h
@@ -13,9 +13,11 @@
 #include "base/callback.h"
 #include "base/callback_list.h"
 #include "content/common/content_export.h"
+#include "url/gurl.h"
 
 namespace content {
 
+class NavigationEntry;
 class BrowserContext;
 class ResourceContext;
 class WebContents;
@@ -52,6 +54,10 @@
 
   typedef std::vector<ZoomLevelChange> ZoomLevelVector;
 
+  // Extracts the URL from NavigationEntry, substituting the error page
+  // URL in the event that the error page is showing.
+  CONTENT_EXPORT static GURL GetURLFromEntry(const NavigationEntry* entry);
+
   CONTENT_EXPORT static HostZoomMap* GetDefaultForBrowserContext(
       BrowserContext* browser_context);
 
@@ -64,6 +70,11 @@
   CONTENT_EXPORT static void SetZoomLevel(const WebContents* web_contents,
                                           double level);
 
+  // Send an IPC to refresh any displayed error page's zoom levels. Needs to
+  // be called since error pages don't get loaded via the normal channel.
+  CONTENT_EXPORT static void SendErrorPageZoomLevelRefresh(
+      const WebContents* web_contents);
+
   // Copy the zoom levels from the given map. Can only be called on the UI
   // thread.
   virtual void CopyFrom(HostZoomMap* copy) = 0;