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