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