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