Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/host_zoom_map_impl.h"
      6 
      7 #include <algorithm>
      8 #include <cmath>
      9 
     10 #include "base/strings/string_piece.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "base/values.h"
     13 #include "content/browser/frame_host/navigation_entry_impl.h"
     14 #include "content/browser/renderer_host/render_process_host_impl.h"
     15 #include "content/browser/renderer_host/render_view_host_impl.h"
     16 #include "content/browser/web_contents/web_contents_impl.h"
     17 #include "content/common/view_messages.h"
     18 #include "content/public/browser/browser_context.h"
     19 #include "content/public/browser/browser_thread.h"
     20 #include "content/public/browser/notification_service.h"
     21 #include "content/public/browser/notification_types.h"
     22 #include "content/public/browser/resource_context.h"
     23 #include "content/public/common/page_zoom.h"
     24 #include "net/base/net_util.h"
     25 
     26 static const char* kHostZoomMapKeyName = "content_host_zoom_map";
     27 
     28 namespace content {
     29 
     30 namespace {
     31 
     32 std::string GetHostFromProcessView(int render_process_id, int render_view_id) {
     33   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     34   RenderViewHost* render_view_host =
     35       RenderViewHost::FromID(render_process_id, render_view_id);
     36   if (!render_view_host)
     37     return std::string();
     38 
     39   WebContents* web_contents = WebContents::FromRenderViewHost(render_view_host);
     40 
     41   NavigationEntry* entry =
     42       web_contents->GetController().GetLastCommittedEntry();
     43   if (!entry)
     44     return std::string();
     45 
     46   return net::GetHostOrSpecFromURL(entry->GetURL());
     47 }
     48 
     49 }  // namespace
     50 
     51 HostZoomMap* HostZoomMap::GetForBrowserContext(BrowserContext* context) {
     52   HostZoomMapImpl* rv = static_cast<HostZoomMapImpl*>(
     53       context->GetUserData(kHostZoomMapKeyName));
     54   if (!rv) {
     55     rv = new HostZoomMapImpl();
     56     context->SetUserData(kHostZoomMapKeyName, rv);
     57   }
     58   return rv;
     59 }
     60 
     61 // Helper function for setting/getting zoom levels for WebContents without
     62 // having to import HostZoomMapImpl everywhere.
     63 double HostZoomMap::GetZoomLevel(const WebContents* web_contents) {
     64   HostZoomMapImpl* host_zoom_map = static_cast<HostZoomMapImpl*>(
     65       HostZoomMap::GetForBrowserContext(web_contents->GetBrowserContext()));
     66   return host_zoom_map->GetZoomLevelForWebContents(
     67       *static_cast<const WebContentsImpl*>(web_contents));
     68 }
     69 
     70 void HostZoomMap::SetZoomLevel(const WebContents* web_contents, double level) {
     71   HostZoomMapImpl* host_zoom_map = static_cast<HostZoomMapImpl*>(
     72       HostZoomMap::GetForBrowserContext(web_contents->GetBrowserContext()));
     73   host_zoom_map->SetZoomLevelForWebContents(
     74       *static_cast<const WebContentsImpl*>(web_contents), level);
     75 }
     76 
     77 HostZoomMapImpl::HostZoomMapImpl()
     78     : default_zoom_level_(0.0) {
     79   registrar_.Add(
     80       this, NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW,
     81       NotificationService::AllSources());
     82 }
     83 
     84 void HostZoomMapImpl::CopyFrom(HostZoomMap* copy_interface) {
     85   // This can only be called on the UI thread to avoid deadlocks, otherwise
     86   //   UI: a.CopyFrom(b);
     87   //   IO: b.CopyFrom(a);
     88   // can deadlock.
     89   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     90   HostZoomMapImpl* copy = static_cast<HostZoomMapImpl*>(copy_interface);
     91   base::AutoLock auto_lock(lock_);
     92   base::AutoLock copy_auto_lock(copy->lock_);
     93   host_zoom_levels_.
     94       insert(copy->host_zoom_levels_.begin(), copy->host_zoom_levels_.end());
     95   for (SchemeHostZoomLevels::const_iterator i(copy->
     96            scheme_host_zoom_levels_.begin());
     97        i != copy->scheme_host_zoom_levels_.end(); ++i) {
     98     scheme_host_zoom_levels_[i->first] = HostZoomLevels();
     99     scheme_host_zoom_levels_[i->first].
    100         insert(i->second.begin(), i->second.end());
    101   }
    102   default_zoom_level_ = copy->default_zoom_level_;
    103 }
    104 
    105 double HostZoomMapImpl::GetZoomLevelForHost(const std::string& host) const {
    106   base::AutoLock auto_lock(lock_);
    107   HostZoomLevels::const_iterator i(host_zoom_levels_.find(host));
    108   return (i == host_zoom_levels_.end()) ? default_zoom_level_ : i->second;
    109 }
    110 
    111 bool HostZoomMapImpl::HasZoomLevel(const std::string& scheme,
    112                                    const std::string& host) const {
    113   base::AutoLock auto_lock(lock_);
    114 
    115   SchemeHostZoomLevels::const_iterator scheme_iterator(
    116       scheme_host_zoom_levels_.find(scheme));
    117 
    118   const HostZoomLevels& zoom_levels =
    119       (scheme_iterator != scheme_host_zoom_levels_.end())
    120           ? scheme_iterator->second
    121           : host_zoom_levels_;
    122 
    123   HostZoomLevels::const_iterator i(zoom_levels.find(host));
    124   return i != zoom_levels.end();
    125 }
    126 
    127 double HostZoomMapImpl::GetZoomLevelForHostAndScheme(
    128     const std::string& scheme,
    129     const std::string& host) const {
    130   {
    131     base::AutoLock auto_lock(lock_);
    132     SchemeHostZoomLevels::const_iterator scheme_iterator(
    133         scheme_host_zoom_levels_.find(scheme));
    134     if (scheme_iterator != scheme_host_zoom_levels_.end()) {
    135       HostZoomLevels::const_iterator i(scheme_iterator->second.find(host));
    136       if (i != scheme_iterator->second.end())
    137         return i->second;
    138     }
    139   }
    140   return GetZoomLevelForHost(host);
    141 }
    142 
    143 HostZoomMap::ZoomLevelVector HostZoomMapImpl::GetAllZoomLevels() const {
    144   HostZoomMap::ZoomLevelVector result;
    145   {
    146     base::AutoLock auto_lock(lock_);
    147     result.reserve(host_zoom_levels_.size() + scheme_host_zoom_levels_.size());
    148     for (HostZoomLevels::const_iterator i = host_zoom_levels_.begin();
    149          i != host_zoom_levels_.end();
    150          ++i) {
    151       ZoomLevelChange change = {HostZoomMap::ZOOM_CHANGED_FOR_HOST,
    152                                 i->first,       // host
    153                                 std::string(),  // scheme
    154                                 i->second       // zoom level
    155       };
    156       result.push_back(change);
    157     }
    158     for (SchemeHostZoomLevels::const_iterator i =
    159              scheme_host_zoom_levels_.begin();
    160          i != scheme_host_zoom_levels_.end();
    161          ++i) {
    162       const std::string& scheme = i->first;
    163       const HostZoomLevels& host_zoom_levels = i->second;
    164       for (HostZoomLevels::const_iterator j = host_zoom_levels.begin();
    165            j != host_zoom_levels.end();
    166            ++j) {
    167         ZoomLevelChange change = {HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST,
    168                                   j->first,  // host
    169                                   scheme,    // scheme
    170                                   j->second  // zoom level
    171         };
    172         result.push_back(change);
    173       }
    174     }
    175   }
    176   return result;
    177 }
    178 
    179 void HostZoomMapImpl::SetZoomLevelForHost(const std::string& host,
    180                                           double level) {
    181   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    182 
    183   {
    184     base::AutoLock auto_lock(lock_);
    185 
    186     if (ZoomValuesEqual(level, default_zoom_level_))
    187       host_zoom_levels_.erase(host);
    188     else
    189       host_zoom_levels_[host] = level;
    190   }
    191 
    192   // TODO(wjmaclean) Should we use a GURL here? crbug.com/384486
    193   SendZoomLevelChange(std::string(), host, level);
    194 
    195   HostZoomMap::ZoomLevelChange change;
    196   change.mode = HostZoomMap::ZOOM_CHANGED_FOR_HOST;
    197   change.host = host;
    198   change.zoom_level = level;
    199 
    200   zoom_level_changed_callbacks_.Notify(change);
    201 }
    202 
    203 void HostZoomMapImpl::SetZoomLevelForHostAndScheme(const std::string& scheme,
    204                                                    const std::string& host,
    205                                                    double level) {
    206   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    207   {
    208     base::AutoLock auto_lock(lock_);
    209     scheme_host_zoom_levels_[scheme][host] = level;
    210   }
    211 
    212   SendZoomLevelChange(scheme, host, level);
    213 
    214   HostZoomMap::ZoomLevelChange change;
    215   change.mode = HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST;
    216   change.host = host;
    217   change.scheme = scheme;
    218   change.zoom_level = level;
    219 
    220   zoom_level_changed_callbacks_.Notify(change);
    221 }
    222 
    223 double HostZoomMapImpl::GetDefaultZoomLevel() const {
    224   return default_zoom_level_;
    225 }
    226 
    227 void HostZoomMapImpl::SetDefaultZoomLevel(double level) {
    228   default_zoom_level_ = level;
    229 }
    230 
    231 scoped_ptr<HostZoomMap::Subscription>
    232 HostZoomMapImpl::AddZoomLevelChangedCallback(
    233     const ZoomLevelChangedCallback& callback) {
    234   return zoom_level_changed_callbacks_.Add(callback);
    235 }
    236 
    237 double HostZoomMapImpl::GetZoomLevelForWebContents(
    238     const WebContentsImpl& web_contents_impl) const {
    239   int render_process_id = web_contents_impl.GetRenderProcessHost()->GetID();
    240   int routing_id = web_contents_impl.GetRenderViewHost()->GetRoutingID();
    241 
    242   if (UsesTemporaryZoomLevel(render_process_id, routing_id))
    243     return GetTemporaryZoomLevel(render_process_id, routing_id);
    244 
    245   // Get the url from the navigation controller directly, as calling
    246   // WebContentsImpl::GetLastCommittedURL() may give us a virtual url that
    247   // is different than is stored in the map.
    248   GURL url;
    249   NavigationEntry* entry =
    250       web_contents_impl.GetController().GetLastCommittedEntry();
    251   // It is possible for a WebContent's zoom level to be queried before
    252   // a navigation has occurred.
    253   if (entry)
    254     url = entry->GetURL();
    255   return GetZoomLevelForHostAndScheme(url.scheme(),
    256                                       net::GetHostOrSpecFromURL(url));
    257 }
    258 
    259 void HostZoomMapImpl::SetZoomLevelForWebContents(
    260     const WebContentsImpl& web_contents_impl,
    261     double level) {
    262   int render_process_id = web_contents_impl.GetRenderProcessHost()->GetID();
    263   int render_view_id = web_contents_impl.GetRenderViewHost()->GetRoutingID();
    264   if (UsesTemporaryZoomLevel(render_process_id, render_view_id)) {
    265     SetTemporaryZoomLevel(render_process_id, render_view_id, level);
    266   } else {
    267     // Get the url from the navigation controller directly, as calling
    268     // WebContentsImpl::GetLastCommittedURL() may give us a virtual url that
    269     // is different than what the render view is using. If the two don't match,
    270     // the attempt to set the zoom will fail.
    271     NavigationEntry* entry =
    272         web_contents_impl.GetController().GetLastCommittedEntry();
    273     // Tests may invoke this function with a null entry, but we don't
    274     // want to save zoom levels in this case.
    275     if (!entry)
    276       return;
    277 
    278     GURL url = entry->GetURL();
    279     SetZoomLevelForHost(net::GetHostOrSpecFromURL(url), level);
    280   }
    281 }
    282 
    283 void HostZoomMapImpl::SetZoomLevelForView(int render_process_id,
    284                                           int render_view_id,
    285                                           double level,
    286                                           const std::string& host) {
    287   if (UsesTemporaryZoomLevel(render_process_id, render_view_id))
    288     SetTemporaryZoomLevel(render_process_id, render_view_id, level);
    289   else
    290     SetZoomLevelForHost(host, level);
    291 }
    292 
    293 bool HostZoomMapImpl::UsesTemporaryZoomLevel(int render_process_id,
    294                                              int render_view_id) const {
    295   RenderViewKey key(render_process_id, render_view_id);
    296 
    297   base::AutoLock auto_lock(lock_);
    298   return ContainsKey(temporary_zoom_levels_, key);
    299 }
    300 
    301 double HostZoomMapImpl::GetTemporaryZoomLevel(int render_process_id,
    302                                               int render_view_id) const {
    303   base::AutoLock auto_lock(lock_);
    304   RenderViewKey key(render_process_id, render_view_id);
    305   if (!ContainsKey(temporary_zoom_levels_, key))
    306     return 0;
    307 
    308   return temporary_zoom_levels_.find(key)->second;
    309 }
    310 
    311 void HostZoomMapImpl::SetTemporaryZoomLevel(int render_process_id,
    312                                             int render_view_id,
    313                                             double level) {
    314   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    315 
    316   {
    317     RenderViewKey key(render_process_id, render_view_id);
    318     base::AutoLock auto_lock(lock_);
    319     temporary_zoom_levels_[key] = level;
    320   }
    321 
    322   RenderViewHost* host =
    323       RenderViewHost::FromID(render_process_id, render_view_id);
    324   host->Send(new ViewMsg_SetZoomLevelForView(render_view_id, true, level));
    325 
    326   HostZoomMap::ZoomLevelChange change;
    327   change.mode = HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM;
    328   change.host = GetHostFromProcessView(render_process_id, render_view_id);
    329   change.zoom_level = level;
    330 
    331   zoom_level_changed_callbacks_.Notify(change);
    332 }
    333 
    334 void HostZoomMapImpl::Observe(int type,
    335                               const NotificationSource& source,
    336                               const NotificationDetails& details) {
    337   switch (type) {
    338     case NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW: {
    339       int render_view_id = Source<RenderViewHost>(source)->GetRoutingID();
    340       int render_process_id =
    341           Source<RenderViewHost>(source)->GetProcess()->GetID();
    342       ClearTemporaryZoomLevel(render_process_id, render_view_id);
    343       break;
    344     }
    345     default:
    346       NOTREACHED() << "Unexpected preference observed.";
    347   }
    348 }
    349 
    350 void HostZoomMapImpl::ClearTemporaryZoomLevel(int render_process_id,
    351                                               int render_view_id) {
    352   {
    353     base::AutoLock auto_lock(lock_);
    354     RenderViewKey key(render_process_id, render_view_id);
    355     TemporaryZoomLevels::iterator it = temporary_zoom_levels_.find(key);
    356     if (it == temporary_zoom_levels_.end())
    357       return;
    358     temporary_zoom_levels_.erase(it);
    359   }
    360   RenderViewHost* host =
    361       RenderViewHost::FromID(render_process_id, render_view_id);
    362   DCHECK(host);
    363   // Send a new zoom level, host-specific if one exists.
    364   host->Send(new ViewMsg_SetZoomLevelForView(
    365       render_view_id,
    366       false,
    367       GetZoomLevelForHost(
    368           GetHostFromProcessView(render_process_id, render_view_id))));
    369 }
    370 
    371 void HostZoomMapImpl::SendZoomLevelChange(const std::string& scheme,
    372                                           const std::string& host,
    373                                           double level) {
    374   for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
    375        !i.IsAtEnd(); i.Advance()) {
    376     RenderProcessHost* render_process_host = i.GetCurrentValue();
    377     if (HostZoomMap::GetForBrowserContext(
    378             render_process_host->GetBrowserContext()) == this) {
    379       render_process_host->Send(
    380           new ViewMsg_SetZoomLevelForCurrentURL(scheme, host, level));
    381     }
    382   }
    383 }
    384 
    385 HostZoomMapImpl::~HostZoomMapImpl() {
    386 }
    387 
    388 }  // namespace content
    389