Home | History | Annotate | Download | only in zoom
      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 "chrome/browser/ui/zoom/zoom_controller.h"
      6 
      7 #include "chrome/browser/ui/sad_tab.h"
      8 #include "chrome/browser/ui/zoom/zoom_event_manager.h"
      9 #include "chrome/browser/ui/zoom/zoom_observer.h"
     10 #include "content/public/browser/host_zoom_map.h"
     11 #include "content/public/browser/navigation_entry.h"
     12 #include "content/public/browser/render_process_host.h"
     13 #include "content/public/browser/render_view_host.h"
     14 #include "content/public/browser/web_contents.h"
     15 #include "content/public/common/page_type.h"
     16 #include "content/public/common/page_zoom.h"
     17 #include "extensions/common/extension.h"
     18 #include "grit/theme_resources.h"
     19 #include "net/base/net_util.h"
     20 
     21 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ZoomController);
     22 
     23 ZoomController::ZoomController(content::WebContents* web_contents)
     24     : content::WebContentsObserver(web_contents),
     25       can_show_bubble_(true),
     26       zoom_mode_(ZOOM_MODE_DEFAULT),
     27       zoom_level_(1.0),
     28       browser_context_(web_contents->GetBrowserContext()) {
     29   // TODO(wjmaclean) Make calls to HostZoomMap::GetDefaultForBrowserContext()
     30   // refer to the webcontents-specific HostZoomMap when that becomes available.
     31   content::HostZoomMap* host_zoom_map =
     32       content::HostZoomMap::GetDefaultForBrowserContext(browser_context_);
     33   zoom_level_ = host_zoom_map->GetDefaultZoomLevel();
     34 
     35   zoom_subscription_ = host_zoom_map->AddZoomLevelChangedCallback(
     36       base::Bind(&ZoomController::OnZoomLevelChanged, base::Unretained(this)));
     37 
     38   UpdateState(std::string());
     39 }
     40 
     41 ZoomController::~ZoomController() {}
     42 
     43 bool ZoomController::IsAtDefaultZoom() const {
     44   return content::ZoomValuesEqual(GetZoomLevel(), GetDefaultZoomLevel());
     45 }
     46 
     47 int ZoomController::GetResourceForZoomLevel() const {
     48   if (IsAtDefaultZoom())
     49     return IDR_ZOOM_NORMAL;
     50   return GetZoomLevel() > GetDefaultZoomLevel() ? IDR_ZOOM_PLUS
     51                                                 : IDR_ZOOM_MINUS;
     52 }
     53 
     54 void ZoomController::AddObserver(ZoomObserver* observer) {
     55   observers_.AddObserver(observer);
     56 }
     57 
     58 void ZoomController::RemoveObserver(ZoomObserver* observer) {
     59   observers_.RemoveObserver(observer);
     60 }
     61 
     62 double ZoomController::GetZoomLevel() const {
     63   return zoom_mode_ == ZOOM_MODE_MANUAL ?
     64              zoom_level_:
     65              content::HostZoomMap::GetZoomLevel(web_contents());
     66 }
     67 
     68 int ZoomController::GetZoomPercent() const {
     69   double zoom_factor = content::ZoomLevelToZoomFactor(GetZoomLevel());
     70   // Round double for return.
     71   return static_cast<int>(zoom_factor * 100 + 0.5);
     72 }
     73 
     74 bool ZoomController::SetZoomLevel(double zoom_level) {
     75   // An extension did not initiate this zoom change.
     76   return SetZoomLevelByExtension(zoom_level, NULL);
     77 }
     78 
     79 bool ZoomController::SetZoomLevelByExtension(
     80     double zoom_level,
     81     const scoped_refptr<const extensions::Extension>& extension) {
     82   content::NavigationEntry* entry =
     83       web_contents()->GetController().GetLastCommittedEntry();
     84   bool is_normal_page =
     85       entry && entry->GetPageType() == content::PAGE_TYPE_NORMAL;
     86   // Cannot zoom in disabled mode. Also, don't allow changing zoom level on
     87   // a crashed tab, an error page or an interstitial page.
     88   if (zoom_mode_ == ZOOM_MODE_DISABLED ||
     89       !web_contents()->GetRenderViewHost()->IsRenderViewLive() ||
     90       !is_normal_page)
     91     return false;
     92 
     93   // Store extension data so that |extension| can be attributed when the zoom
     94   // change completes. We expect that by the time this function returns that
     95   // any observers that require this information will have requested it.
     96   last_extension_ = extension;
     97 
     98   // Do not actually rescale the page in manual mode.
     99   if (zoom_mode_ == ZOOM_MODE_MANUAL) {
    100     double old_zoom_level = zoom_level_;
    101     zoom_level_ = zoom_level;
    102 
    103     // TODO(wjmaclean) Do we care about filling in host/scheme here?
    104     content::HostZoomMap::ZoomLevelChange change;
    105     change.mode = content::HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM;
    106     change.zoom_level = zoom_level;
    107     ZoomEventManager::GetForBrowserContext(browser_context_)->
    108         OnZoomLevelChanged(change);
    109 
    110     ZoomChangedEventData zoom_change_data(web_contents(),
    111                                           old_zoom_level,
    112                                           zoom_level_,
    113                                           zoom_mode_,
    114                                           false /* can_show_bubble */);
    115     FOR_EACH_OBSERVER(
    116         ZoomObserver, observers_, OnZoomChanged(zoom_change_data));
    117 
    118     last_extension_ = NULL;
    119     return true;
    120   }
    121 
    122   content::HostZoomMap* zoom_map =
    123       content::HostZoomMap::GetDefaultForBrowserContext(browser_context_);
    124   DCHECK(zoom_map);
    125   DCHECK(!event_data_);
    126   event_data_.reset(new ZoomChangedEventData(web_contents(),
    127                                              GetZoomLevel(),
    128                                              zoom_level,
    129                                              zoom_mode_,
    130                                              false /* can_show_bubble */));
    131   int render_process_id = web_contents()->GetRenderProcessHost()->GetID();
    132   int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID();
    133   if (zoom_mode_ == ZOOM_MODE_ISOLATED ||
    134       zoom_map->UsesTemporaryZoomLevel(render_process_id, render_view_id)) {
    135     zoom_map->SetTemporaryZoomLevel(
    136         render_process_id, render_view_id, zoom_level);
    137   } else {
    138     if (!entry) {
    139       last_extension_ = NULL;
    140       return false;
    141     }
    142     std::string host = net::GetHostOrSpecFromURL(entry->GetURL());
    143     zoom_map->SetZoomLevelForHost(host, zoom_level);
    144   }
    145 
    146   DCHECK(!event_data_);
    147   last_extension_ = NULL;
    148   return true;
    149 }
    150 
    151 void ZoomController::SetZoomMode(ZoomMode new_mode) {
    152   if (new_mode == zoom_mode_)
    153     return;
    154 
    155   content::HostZoomMap* zoom_map =
    156       content::HostZoomMap::GetDefaultForBrowserContext(browser_context_);
    157   DCHECK(zoom_map);
    158   int render_process_id = web_contents()->GetRenderProcessHost()->GetID();
    159   int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID();
    160   double original_zoom_level = GetZoomLevel();
    161 
    162   DCHECK(!event_data_);
    163   event_data_.reset(new ZoomChangedEventData(web_contents(),
    164                                              original_zoom_level,
    165                                              original_zoom_level,
    166                                              new_mode,
    167                                              new_mode != ZOOM_MODE_DEFAULT));
    168 
    169   switch (new_mode) {
    170     case ZOOM_MODE_DEFAULT: {
    171       content::NavigationEntry* entry =
    172           web_contents()->GetController().GetLastCommittedEntry();
    173 
    174       if (entry) {
    175         GURL url = entry->GetURL();
    176         std::string host = net::GetHostOrSpecFromURL(url);
    177 
    178         if (zoom_map->HasZoomLevel(url.scheme(), host)) {
    179           // If there are other tabs with the same origin, then set this tab's
    180           // zoom level to match theirs. The temporary zoom level will be
    181           // cleared below, but this call will make sure this tab re-draws at
    182           // the correct zoom level.
    183           double origin_zoom_level =
    184               zoom_map->GetZoomLevelForHostAndScheme(url.scheme(), host);
    185           event_data_->new_zoom_level = origin_zoom_level;
    186           zoom_map->SetTemporaryZoomLevel(
    187               render_process_id, render_view_id, origin_zoom_level);
    188         } else {
    189           // The host will need a level prior to removing the temporary level.
    190           // We don't want the zoom level to change just because we entered
    191           // default mode.
    192           zoom_map->SetZoomLevelForHost(host, original_zoom_level);
    193         }
    194       }
    195       // Remove per-tab zoom data for this tab. No event callback expected.
    196       zoom_map->ClearTemporaryZoomLevel(render_process_id, render_view_id);
    197       break;
    198     }
    199     case ZOOM_MODE_ISOLATED: {
    200       // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the
    201       // page needs an initial isolated zoom back to the same level it was at
    202       // in the other mode.
    203       if (zoom_mode_ != ZOOM_MODE_DISABLED) {
    204         zoom_map->SetTemporaryZoomLevel(
    205             render_process_id, render_view_id, original_zoom_level);
    206       } else {
    207         // When we don't call any HostZoomMap set functions, we send the event
    208         // manually.
    209         FOR_EACH_OBSERVER(
    210             ZoomObserver, observers_, OnZoomChanged(*event_data_));
    211         event_data_.reset();
    212       }
    213       break;
    214     }
    215     case ZOOM_MODE_MANUAL: {
    216       // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the
    217       // page needs to be resized to the default zoom. While in manual mode,
    218       // the zoom level is handled independently.
    219       if (zoom_mode_ != ZOOM_MODE_DISABLED) {
    220         zoom_map->SetTemporaryZoomLevel(
    221             render_process_id, render_view_id, GetDefaultZoomLevel());
    222         zoom_level_ = original_zoom_level;
    223       } else {
    224         // When we don't call any HostZoomMap set functions, we send the event
    225         // manually.
    226         FOR_EACH_OBSERVER(
    227             ZoomObserver, observers_, OnZoomChanged(*event_data_));
    228         event_data_.reset();
    229       }
    230       break;
    231     }
    232     case ZOOM_MODE_DISABLED: {
    233       // The page needs to be zoomed back to default before disabling the zoom
    234       zoom_map->SetTemporaryZoomLevel(
    235           render_process_id, render_view_id, GetDefaultZoomLevel());
    236       break;
    237     }
    238   }
    239   // Any event data we've stored should have been consumed by this point.
    240   DCHECK(!event_data_);
    241 
    242   zoom_mode_ = new_mode;
    243 }
    244 
    245 void ZoomController::DidNavigateMainFrame(
    246     const content::LoadCommittedDetails& details,
    247     const content::FrameNavigateParams& params) {
    248   // If the main frame's content has changed, the new page may have a different
    249   // zoom level from the old one.
    250   UpdateState(std::string());
    251 }
    252 
    253 void ZoomController::WebContentsDestroyed() {
    254   // At this point we should no longer be sending any zoom events with this
    255   // WebContents.
    256   observers_.Clear();
    257 }
    258 
    259 void ZoomController::OnZoomLevelChanged(
    260     const content::HostZoomMap::ZoomLevelChange& change) {
    261   UpdateState(change.host);
    262 }
    263 
    264 void ZoomController::UpdateState(const std::string& host) {
    265   // If |host| is empty, all observers should be updated.
    266   if (!host.empty()) {
    267     // Use the navigation entry's URL instead of the WebContents' so virtual
    268     // URLs work (e.g. chrome://settings). http://crbug.com/153950
    269     content::NavigationEntry* entry =
    270         web_contents()->GetController().GetLastCommittedEntry();
    271     if (!entry ||
    272         host != net::GetHostOrSpecFromURL(entry->GetURL())) {
    273       return;
    274     }
    275   }
    276 
    277   // The zoom bubble should not be shown for zoom changes where the host is
    278   // empty.
    279   bool can_show_bubble = can_show_bubble_ && !host.empty();
    280 
    281   if (event_data_) {
    282     // For state changes initiated within the ZoomController, information about
    283     // the change should be sent.
    284     ZoomChangedEventData zoom_change_data = *event_data_;
    285     event_data_.reset();
    286     zoom_change_data.can_show_bubble = can_show_bubble;
    287     FOR_EACH_OBSERVER(
    288         ZoomObserver, observers_, OnZoomChanged(zoom_change_data));
    289   } else {
    290     // TODO(wjmaclean) Should we consider having HostZoomMap send both old and
    291     // new zoom levels here?
    292     double zoom_level = GetZoomLevel();
    293     ZoomChangedEventData zoom_change_data(
    294         web_contents(), zoom_level, zoom_level, zoom_mode_, can_show_bubble);
    295     FOR_EACH_OBSERVER(
    296         ZoomObserver, observers_, OnZoomChanged(zoom_change_data));
    297   }
    298 }
    299