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 391