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
26namespace content {
27
28namespace {
29
30const char kHostZoomMapKeyName[] = "content_host_zoom_map";
31
32std::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
51HostZoomMap* 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.
63double 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
71void 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
79HostZoomMapImpl::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
86void 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
107double 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
113bool 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
129double 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
145HostZoomMap::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
181void 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
205void 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
225double HostZoomMapImpl::GetDefaultZoomLevel() const {
226  return default_zoom_level_;
227}
228
229void HostZoomMapImpl::SetDefaultZoomLevel(double level) {
230  default_zoom_level_ = level;
231}
232
233scoped_ptr<HostZoomMap::Subscription>
234HostZoomMapImpl::AddZoomLevelChangedCallback(
235    const ZoomLevelChangedCallback& callback) {
236  return zoom_level_changed_callbacks_.Add(callback);
237}
238
239double 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
261void 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
285void 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
295bool 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
303double 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
313void 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
336void 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
352void 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
373void 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
387HostZoomMapImpl::~HostZoomMapImpl() {
388}
389
390}  // namespace content
391