Zoom Extension API (content changes)

Changes to content/ needed to facilitate the new zoom extension API,
which is outlined in:

https://docs.google.com/a/chromium.org/document/d/1sCZjx1J3_M2a02T8NXd-ufGKZDoBHI5Ixis1DaGLQCA/edit?usp=sharing

Based on code from https://codereview.chromium.org/226523006/, with
various fixes and rebased against changes in
https://codereview.chromium.org/287093002/

Must land before https://codereview.chromium.org/301733006/.

BUG=30583

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@277153 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/browser/host_zoom_map_impl.cc b/content/browser/host_zoom_map_impl.cc
index c52d54f8..1b5f7a2f 100644
--- a/content/browser/host_zoom_map_impl.cc
+++ b/content/browser/host_zoom_map_impl.cc
@@ -27,6 +27,27 @@
 
 namespace content {
 
+namespace {
+
+std::string GetHostFromProcessView(int render_process_id, int render_view_id) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  RenderViewHost* render_view_host =
+      RenderViewHost::FromID(render_process_id, render_view_id);
+  if (!render_view_host)
+    return std::string();
+
+  WebContents* web_contents = WebContents::FromRenderViewHost(render_view_host);
+
+  NavigationEntry* entry =
+      web_contents->GetController().GetLastCommittedEntry();
+  if (!entry)
+    return std::string();
+
+  return net::GetHostOrSpecFromURL(entry->GetURL());
+}
+
+}  // namespace
+
 HostZoomMap* HostZoomMap::GetForBrowserContext(BrowserContext* context) {
   HostZoomMapImpl* rv = static_cast<HostZoomMapImpl*>(
       context->GetUserData(kHostZoomMapKeyName));
@@ -87,6 +108,22 @@
   return (i == host_zoom_levels_.end()) ? default_zoom_level_ : i->second;
 }
 
+bool HostZoomMapImpl::HasZoomLevel(const std::string& scheme,
+                                   const std::string& host) const {
+  base::AutoLock auto_lock(lock_);
+
+  SchemeHostZoomLevels::const_iterator scheme_iterator(
+      scheme_host_zoom_levels_.find(scheme));
+
+  const HostZoomLevels& zoom_levels =
+      (scheme_iterator != scheme_host_zoom_levels_.end())
+          ? scheme_iterator->second
+          : host_zoom_levels_;
+
+  HostZoomLevels::const_iterator i(zoom_levels.find(host));
+  return i != zoom_levels.end();
+}
+
 double HostZoomMapImpl::GetZoomLevelForHostAndScheme(
     const std::string& scheme,
     const std::string& host) const {
@@ -152,16 +189,9 @@
       host_zoom_levels_[host] = level;
   }
 
-  // Notify renderers from this browser context.
-  for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
-       !i.IsAtEnd(); i.Advance()) {
-    RenderProcessHost* render_process_host = i.GetCurrentValue();
-    if (HostZoomMap::GetForBrowserContext(
-            render_process_host->GetBrowserContext()) == this) {
-      render_process_host->Send(
-          new ViewMsg_SetZoomLevelForCurrentURL(std::string(), host, level));
-    }
-  }
+  // TODO(wjmaclean) Should we use a GURL here? crbug.com/384486
+  SendZoomLevelChange(std::string(), host, level);
+
   HostZoomMap::ZoomLevelChange change;
   change.mode = HostZoomMap::ZOOM_CHANGED_FOR_HOST;
   change.host = host;
@@ -179,16 +209,7 @@
     scheme_host_zoom_levels_[scheme][host] = level;
   }
 
-  // Notify renderers from this browser context.
-  for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
-       !i.IsAtEnd(); i.Advance()) {
-    RenderProcessHost* render_process_host = i.GetCurrentValue();
-    if (HostZoomMap::GetForBrowserContext(
-            render_process_host->GetBrowserContext()) == this) {
-      render_process_host->Send(
-          new ViewMsg_SetZoomLevelForCurrentURL(scheme, host, level));
-    }
-  }
+  SendZoomLevelChange(scheme, host, level);
 
   HostZoomMap::ZoomLevelChange change;
   change.mode = HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST;
@@ -271,45 +292,20 @@
 
 bool HostZoomMapImpl::UsesTemporaryZoomLevel(int render_process_id,
                                              int render_view_id) const {
-  TemporaryZoomLevel zoom_level(render_process_id, render_view_id);
+  RenderViewKey key(render_process_id, render_view_id);
 
   base::AutoLock auto_lock(lock_);
-  TemporaryZoomLevels::const_iterator it = std::find(
-      temporary_zoom_levels_.begin(), temporary_zoom_levels_.end(), zoom_level);
-  return it != temporary_zoom_levels_.end();
-}
-
-void HostZoomMapImpl::SetUsesTemporaryZoomLevel(
-    int render_process_id,
-    int render_view_id,
-    bool uses_temporary_zoom_level) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-
-  TemporaryZoomLevel zoom_level(
-      render_process_id, render_view_id, default_zoom_level_);
-
-  base::AutoLock auto_lock(lock_);
-  TemporaryZoomLevels::iterator it = std::find(
-      temporary_zoom_levels_.begin(), temporary_zoom_levels_.end(), zoom_level);
-  if (uses_temporary_zoom_level) {
-    if (it == temporary_zoom_levels_.end())
-      temporary_zoom_levels_.push_back(zoom_level);
-  } else if (it != temporary_zoom_levels_.end()) {
-      temporary_zoom_levels_.erase(it);
-  }
+  return ContainsKey(temporary_zoom_levels_, key);
 }
 
 double HostZoomMapImpl::GetTemporaryZoomLevel(int render_process_id,
                                               int render_view_id) const {
   base::AutoLock auto_lock(lock_);
-  for (size_t i = 0; i < temporary_zoom_levels_.size(); ++i) {
-    if (temporary_zoom_levels_[i].render_process_id == render_process_id &&
-        temporary_zoom_levels_[i].render_view_id == render_view_id) {
-      return temporary_zoom_levels_[i].zoom_level;
-    }
-  }
+  RenderViewKey key(render_process_id, render_view_id);
+  if (!ContainsKey(temporary_zoom_levels_, key))
+    return 0;
 
-  return 0;
+  return temporary_zoom_levels_.find(key)->second;
 }
 
 void HostZoomMapImpl::SetTemporaryZoomLevel(int render_process_id,
@@ -318,28 +314,18 @@
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
   {
+    RenderViewKey key(render_process_id, render_view_id);
     base::AutoLock auto_lock(lock_);
-    size_t i;
-    for (i = 0; i < temporary_zoom_levels_.size(); ++i) {
-      if (temporary_zoom_levels_[i].render_process_id == render_process_id &&
-          temporary_zoom_levels_[i].render_view_id == render_view_id) {
-        if (level) {
-          temporary_zoom_levels_[i].zoom_level = level;
-        } else {
-          temporary_zoom_levels_.erase(temporary_zoom_levels_.begin() + i);
-        }
-        break;
-      }
-    }
-
-    if (level && i == temporary_zoom_levels_.size()) {
-      TemporaryZoomLevel temp(render_process_id, render_view_id, level);
-      temporary_zoom_levels_.push_back(temp);
-    }
+    temporary_zoom_levels_[key] = level;
   }
 
+  RenderViewHost* host =
+      RenderViewHost::FromID(render_process_id, render_view_id);
+  host->Send(new ViewMsg_SetZoomLevelForView(render_view_id, true, level));
+
   HostZoomMap::ZoomLevelChange change;
   change.mode = HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM;
+  change.host = GetHostFromProcessView(render_process_id, render_view_id);
   change.zoom_level = level;
 
   zoom_level_changed_callbacks_.Notify(change);
@@ -350,18 +336,10 @@
                               const NotificationDetails& details) {
   switch (type) {
     case NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW: {
-      base::AutoLock auto_lock(lock_);
       int render_view_id = Source<RenderViewHost>(source)->GetRoutingID();
       int render_process_id =
           Source<RenderViewHost>(source)->GetProcess()->GetID();
-
-      for (size_t i = 0; i < temporary_zoom_levels_.size(); ++i) {
-        if (temporary_zoom_levels_[i].render_process_id == render_process_id &&
-            temporary_zoom_levels_[i].render_view_id == render_view_id) {
-          temporary_zoom_levels_.erase(temporary_zoom_levels_.begin() + i);
-          break;
-        }
-      }
+      ClearTemporaryZoomLevel(render_process_id, render_view_id);
       break;
     }
     default:
@@ -369,28 +347,42 @@
   }
 }
 
+void HostZoomMapImpl::ClearTemporaryZoomLevel(int render_process_id,
+                                              int render_view_id) {
+  {
+    base::AutoLock auto_lock(lock_);
+    RenderViewKey key(render_process_id, render_view_id);
+    TemporaryZoomLevels::iterator it = temporary_zoom_levels_.find(key);
+    if (it == temporary_zoom_levels_.end())
+      return;
+    temporary_zoom_levels_.erase(it);
+  }
+  RenderViewHost* host =
+      RenderViewHost::FromID(render_process_id, render_view_id);
+  DCHECK(host);
+  // Send a new zoom level, host-specific if one exists.
+  host->Send(new ViewMsg_SetZoomLevelForView(
+      render_view_id,
+      false,
+      GetZoomLevelForHost(
+          GetHostFromProcessView(render_process_id, render_view_id))));
+}
+
+void HostZoomMapImpl::SendZoomLevelChange(const std::string& scheme,
+                                          const std::string& host,
+                                          double level) {
+  for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
+       !i.IsAtEnd(); i.Advance()) {
+    RenderProcessHost* render_process_host = i.GetCurrentValue();
+    if (HostZoomMap::GetForBrowserContext(
+            render_process_host->GetBrowserContext()) == this) {
+      render_process_host->Send(
+          new ViewMsg_SetZoomLevelForCurrentURL(scheme, host, level));
+    }
+  }
+}
+
 HostZoomMapImpl::~HostZoomMapImpl() {
 }
 
-HostZoomMapImpl::TemporaryZoomLevel::TemporaryZoomLevel(int process_id,
-                                                        int view_id,
-                                                        double level)
-    : render_process_id(process_id),
-      render_view_id(view_id),
-      zoom_level(level) {
-}
-
-HostZoomMapImpl::TemporaryZoomLevel::TemporaryZoomLevel(int process_id,
-                                                        int view_id)
-    : render_process_id(process_id),
-      render_view_id(view_id),
-      zoom_level(0.0) {
-}
-
-bool HostZoomMapImpl::TemporaryZoomLevel::operator==(
-    const TemporaryZoomLevel& other) const {
-  return other.render_process_id == render_process_id &&
-         other.render_view_id == render_view_id;
-}
-
 }  // namespace content
diff --git a/content/browser/host_zoom_map_impl.h b/content/browser/host_zoom_map_impl.h
index 0ad4fec..821fa0e4 100644
--- a/content/browser/host_zoom_map_impl.h
+++ b/content/browser/host_zoom_map_impl.h
@@ -35,6 +35,9 @@
   virtual double GetZoomLevelForHostAndScheme(
       const std::string& scheme,
       const std::string& host) const OVERRIDE;
+  // TODO(wjmaclean) Should we use a GURL here? crbug.com/384486
+  virtual bool HasZoomLevel(const std::string& scheme,
+                            const std::string& host) const OVERRIDE;
   virtual ZoomLevelVector GetAllZoomLevels() const OVERRIDE;
   virtual void SetZoomLevelForHost(
       const std::string& host,
@@ -43,6 +46,14 @@
       const std::string& scheme,
       const std::string& host,
       double level) OVERRIDE;
+  virtual bool UsesTemporaryZoomLevel(int render_process_id,
+                                      int render_view_id) const OVERRIDE;
+  virtual void SetTemporaryZoomLevel(int render_process_id,
+                                     int render_view_id,
+                                     double level) OVERRIDE;
+
+  virtual void ClearTemporaryZoomLevel(int render_process_id,
+                                       int render_view_id) OVERRIDE;
   virtual double GetDefaultZoomLevel() const OVERRIDE;
   virtual void SetDefaultZoomLevel(double level) OVERRIDE;
   virtual scoped_ptr<Subscription> AddZoomLevelChangedCallback(
@@ -66,17 +77,6 @@
                            double level,
                            const std::string& host);
 
-  // Returns whether the view manages its zoom level independently of other tabs
-  // displaying content from the same host.
-  bool UsesTemporaryZoomLevel(int render_process_id, int render_view_id) const;
-
-  // Sets whether the view manages its zoom level independently of other tabs
-  // displaying content from the same host, based on whether
-  // |uses_temporary_zoom_level| is true.
-  void SetUsesTemporaryZoomLevel(int render_process_id,
-                                 int render_view_id,
-                                 bool uses_temporary_zoom_level);
-
   // Returns the temporary zoom level that's only valid for the lifetime of
   // the given WebContents (i.e. isn't saved and doesn't affect other
   // WebContentses) if it exists, the default zoom level otherwise.
@@ -85,14 +85,6 @@
   double GetTemporaryZoomLevel(int render_process_id,
                                int render_view_id) const;
 
-  // Sets the temporary zoom level that's only valid for the lifetime of this
-  // WebContents.
-  //
-  // This should only be called on the UI thread.
-  void SetTemporaryZoomLevel(int render_process_id,
-                             int render_view_id,
-                             double level);
-
   // NotificationObserver implementation.
   virtual void Observe(int type,
                        const NotificationSource& source,
@@ -102,20 +94,30 @@
   typedef std::map<std::string, double> HostZoomLevels;
   typedef std::map<std::string, HostZoomLevels> SchemeHostZoomLevels;
 
-  struct TemporaryZoomLevel {
-    TemporaryZoomLevel(int process_id, int view_id, double level);
-    TemporaryZoomLevel(int process_id, int view_id);
-    bool operator==(const TemporaryZoomLevel& other) const;
-
+  struct RenderViewKey {
     int render_process_id;
     int render_view_id;
-    double zoom_level;
+    RenderViewKey(int render_process_id, int render_view_id)
+        : render_process_id(render_process_id),
+          render_view_id(render_view_id) {}
+    bool operator<(const RenderViewKey& other) const {
+      return render_process_id < other.render_process_id ||
+             ((render_process_id == other.render_process_id) &&
+              (render_view_id < other.render_view_id));
+    }
   };
 
-  typedef std::vector<TemporaryZoomLevel> TemporaryZoomLevels;
+  typedef std::map<RenderViewKey, double> TemporaryZoomLevels;
 
   double GetZoomLevelForHost(const std::string& host) const;
 
+  // Notifies the renderers from this browser context to change the zoom level
+  // for the specified host and scheme.
+  // TODO(wjmaclean) Should we use a GURL here? crbug.com/384486
+  void SendZoomLevelChange(const std::string& scheme,
+                           const std::string& host,
+                           double level);
+
   // Callbacks called when zoom level changes.
   base::CallbackList<void(const ZoomLevelChange&)>
       zoom_level_changed_callbacks_;
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 2e9d720..582496e 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -1188,10 +1188,14 @@
     bool uses_temporary_zoom_level) {
   delegate_->DocumentAvailableInMainFrame(this);
 
+  if (!uses_temporary_zoom_level)
+    return;
+
   HostZoomMapImpl* host_zoom_map = static_cast<HostZoomMapImpl*>(
       HostZoomMap::GetForBrowserContext(GetProcess()->GetBrowserContext()));
-  host_zoom_map->SetUsesTemporaryZoomLevel(
-      GetProcess()->GetID(), GetRoutingID(), uses_temporary_zoom_level);
+  host_zoom_map->SetTemporaryZoomLevel(GetProcess()->GetID(),
+                                       GetRoutingID(),
+                                       host_zoom_map->GetDefaultZoomLevel());
 }
 
 void RenderViewHostImpl::OnToggleFullscreen(bool enter_fullscreen) {
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index a88af42..3815910 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -689,6 +689,11 @@
                      std::string /* host */,
                      double /* zoom_level */)
 
+// Set the zoom level for a particular render view.
+IPC_MESSAGE_ROUTED2(ViewMsg_SetZoomLevelForView,
+                    bool /* uses_temporary_zoom_level */,
+                    double /* zoom_level */)
+
 // Change encoding of page in the renderer.
 IPC_MESSAGE_ROUTED1(ViewMsg_SetPageEncoding,
                     std::string /*new encoding name*/)
diff --git a/content/public/browser/host_zoom_map.h b/content/public/browser/host_zoom_map.h
index e98e74e4..e65b443 100644
--- a/content/public/browser/host_zoom_map.h
+++ b/content/public/browser/host_zoom_map.h
@@ -78,7 +78,14 @@
       const std::string& scheme,
       const std::string& host) const = 0;
 
-  // Returns all non-temporary zoom levels. Can only be called on any thread.
+  // Returns true if the specified |scheme| and/or |host| has a zoom level
+  // currently set.
+  //
+  // This may be called on any thread.
+  virtual bool HasZoomLevel(const std::string& scheme,
+                            const std::string& host) const = 0;
+
+  // Returns all non-temporary zoom levels. Can be called on any thread.
   virtual ZoomLevelVector GetAllZoomLevels() const = 0;
 
   // Here |host| is the host portion of URL, or (in the absence of a host)
@@ -102,6 +109,25 @@
                                             const std::string& host,
                                             double level) = 0;
 
+  // Returns whether the view manages its zoom level independently of other
+  // views displaying content from the same host.
+  virtual bool UsesTemporaryZoomLevel(int render_process_id,
+                                      int render_view_id) const = 0;
+
+  // Sets the temporary zoom level that's only valid for the lifetime of this
+  // WebContents.
+  //
+  // This should only be called on the UI thread.
+  virtual void SetTemporaryZoomLevel(int render_process_id,
+                                     int render_view_id,
+                                     double level) = 0;
+
+  // Clears the temporary zoom level stored for this WebContents.
+  //
+  // This should only be called on the UI thread.
+  virtual void ClearTemporaryZoomLevel(int render_process_id,
+                                       int render_view_id) = 0;
+
   // Get/Set the default zoom level for pages that don't override it.
   virtual double GetDefaultZoomLevel() const = 0;
   virtual void SetDefaultZoomLevel(double level) = 0;;
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index df91a8dd..e66bd92 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -196,7 +196,9 @@
     GURL url(document.url());
     // Empty scheme works as wildcard that matches any scheme,
     if ((net::GetHostOrSpecFromURL(url) == host_) &&
-        (scheme_.empty() || scheme_ == url.scheme())) {
+        (scheme_.empty() || scheme_ == url.scheme()) &&
+        !static_cast<RenderViewImpl*>(render_view)
+             ->uses_temporary_zoom_level()) {
       webview->hidePopups();
       webview->setZoomLevel(zoom_level_);
     }
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 6634d03..d9c65a9 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -647,6 +647,7 @@
       history_list_length_(0),
       frames_in_progress_(0),
       target_url_status_(TARGET_NONE),
+      uses_temporary_zoom_level_(false),
 #if defined(OS_ANDROID)
       top_controls_constraints_(cc::BOTH),
 #endif
@@ -1081,6 +1082,8 @@
     IPC_MESSAGE_HANDLER(ViewMsg_Zoom, OnZoom)
     IPC_MESSAGE_HANDLER(ViewMsg_SetZoomLevelForLoadingURL,
                         OnSetZoomLevelForLoadingURL)
+    IPC_MESSAGE_HANDLER(ViewMsg_SetZoomLevelForView,
+                        OnSetZoomLevelForView)
     IPC_MESSAGE_HANDLER(ViewMsg_SetPageEncoding, OnSetPageEncoding)
     IPC_MESSAGE_HANDLER(ViewMsg_ResetPageEncodingToDefault,
                         OnResetPageEncodingToDefault)
@@ -2614,6 +2617,14 @@
 #endif
 }
 
+void RenderViewImpl::OnSetZoomLevelForView(bool uses_temporary_zoom_level,
+                                           double level) {
+  uses_temporary_zoom_level_ = uses_temporary_zoom_level;
+
+  webview()->hidePopups();
+  webview()->setZoomLevel(level);
+}
+
 void RenderViewImpl::OnSetPageEncoding(const std::string& encoding_name) {
   webview()->setPageEncoding(WebString::fromUTF8(encoding_name));
 }
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 1797e00c..4e978f83 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -510,6 +510,7 @@
                                       TopControlsState current,
                                       bool animate) OVERRIDE;
 #endif
+  bool uses_temporary_zoom_level() const { return uses_temporary_zoom_level_; }
 
   // Please do not add your stuff randomly to the end here. If there is an
   // appropriate section, add it there. If not, there are some random functions
@@ -742,6 +743,7 @@
   void OnSetRendererPrefs(const RendererPreferences& renderer_prefs);
   void OnSetWebUIProperty(const std::string& name, const std::string& value);
   void OnSetZoomLevelForLoadingURL(const GURL& url, double zoom_level);
+  void OnSetZoomLevelForView(bool uses_temporary_zoom_level, double level);
   void OnStop();
   void OnStopFinding(StopFindAction action);
   void OnSuppressDialogsUntilSwapOut();
@@ -1010,6 +1012,9 @@
   // The next target URL we want to send to the browser.
   GURL pending_target_url_;
 
+  // Indicates whether this view overrides url-based zoom settings.
+  bool uses_temporary_zoom_level_;
+
 #if defined(OS_ANDROID)
   // Cache the old top controls state constraints. Used when updating
   // current value only without altering the constraints.