Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / browser / host_zoom_map_impl.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 "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