// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/host_zoom_map_impl.h" #include <algorithm> #include <cmath> #include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "content/browser/frame_host/navigation_entry_impl.h" #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/view_messages.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/resource_context.h" #include "content/public/common/page_zoom.h" #include "net/base/net_util.h" namespace content { namespace { const char kHostZoomMapKeyName[] = "content_host_zoom_map"; std::string GetHostFromProcessView(int render_process_id, int render_view_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); RenderViewHost* render_view_host = RenderViewHost::FromID(render_process_id, render_view_id); if (!render_view_host) return std::string(); WebContents* web_contents = WebContents::FromRenderViewHost(render_view_host); NavigationEntry* entry = web_contents->GetController().GetLastCommittedEntry(); if (!entry) return std::string(); return net::GetHostOrSpecFromURL(entry->GetURL()); } } // namespace HostZoomMap* HostZoomMap::GetDefaultForBrowserContext(BrowserContext* context) { HostZoomMapImpl* rv = static_cast<HostZoomMapImpl*>( context->GetUserData(kHostZoomMapKeyName)); if (!rv) { rv = new HostZoomMapImpl(); context->SetUserData(kHostZoomMapKeyName, rv); } return rv; } // Helper function for setting/getting zoom levels for WebContents without // having to import HostZoomMapImpl everywhere. double HostZoomMap::GetZoomLevel(const WebContents* web_contents) { HostZoomMapImpl* host_zoom_map = static_cast<HostZoomMapImpl*>(HostZoomMap::GetDefaultForBrowserContext( web_contents->GetBrowserContext())); return host_zoom_map->GetZoomLevelForWebContents( *static_cast<const WebContentsImpl*>(web_contents)); } void HostZoomMap::SetZoomLevel(const WebContents* web_contents, double level) { HostZoomMapImpl* host_zoom_map = static_cast<HostZoomMapImpl*>(HostZoomMap::GetDefaultForBrowserContext( web_contents->GetBrowserContext())); host_zoom_map->SetZoomLevelForWebContents( *static_cast<const WebContentsImpl*>(web_contents), level); } HostZoomMapImpl::HostZoomMapImpl() : default_zoom_level_(0.0) { registrar_.Add( this, NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW, NotificationService::AllSources()); } void HostZoomMapImpl::CopyFrom(HostZoomMap* copy_interface) { // This can only be called on the UI thread to avoid deadlocks, otherwise // UI: a.CopyFrom(b); // IO: b.CopyFrom(a); // can deadlock. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); HostZoomMapImpl* copy = static_cast<HostZoomMapImpl*>(copy_interface); base::AutoLock auto_lock(lock_); base::AutoLock copy_auto_lock(copy->lock_); host_zoom_levels_. insert(copy->host_zoom_levels_.begin(), copy->host_zoom_levels_.end()); for (SchemeHostZoomLevels::const_iterator i(copy-> scheme_host_zoom_levels_.begin()); i != copy->scheme_host_zoom_levels_.end(); ++i) { scheme_host_zoom_levels_[i->first] = HostZoomLevels(); scheme_host_zoom_levels_[i->first]. insert(i->second.begin(), i->second.end()); } default_zoom_level_ = copy->default_zoom_level_; } double HostZoomMapImpl::GetZoomLevelForHost(const std::string& host) const { base::AutoLock auto_lock(lock_); HostZoomLevels::const_iterator i(host_zoom_levels_.find(host)); return (i == host_zoom_levels_.end()) ? default_zoom_level_ : i->second; } bool HostZoomMapImpl::HasZoomLevel(const std::string& scheme, const std::string& host) const { base::AutoLock auto_lock(lock_); SchemeHostZoomLevels::const_iterator scheme_iterator( scheme_host_zoom_levels_.find(scheme)); const HostZoomLevels& zoom_levels = (scheme_iterator != scheme_host_zoom_levels_.end()) ? scheme_iterator->second : host_zoom_levels_; HostZoomLevels::const_iterator i(zoom_levels.find(host)); return i != zoom_levels.end(); } double HostZoomMapImpl::GetZoomLevelForHostAndScheme( const std::string& scheme, const std::string& host) const { { base::AutoLock auto_lock(lock_); SchemeHostZoomLevels::const_iterator scheme_iterator( scheme_host_zoom_levels_.find(scheme)); if (scheme_iterator != scheme_host_zoom_levels_.end()) { HostZoomLevels::const_iterator i(scheme_iterator->second.find(host)); if (i != scheme_iterator->second.end()) return i->second; } } return GetZoomLevelForHost(host); } HostZoomMap::ZoomLevelVector HostZoomMapImpl::GetAllZoomLevels() const { HostZoomMap::ZoomLevelVector result; { base::AutoLock auto_lock(lock_); result.reserve(host_zoom_levels_.size() + scheme_host_zoom_levels_.size()); for (HostZoomLevels::const_iterator i = host_zoom_levels_.begin(); i != host_zoom_levels_.end(); ++i) { ZoomLevelChange change = {HostZoomMap::ZOOM_CHANGED_FOR_HOST, i->first, // host std::string(), // scheme i->second // zoom level }; result.push_back(change); } for (SchemeHostZoomLevels::const_iterator i = scheme_host_zoom_levels_.begin(); i != scheme_host_zoom_levels_.end(); ++i) { const std::string& scheme = i->first; const HostZoomLevels& host_zoom_levels = i->second; for (HostZoomLevels::const_iterator j = host_zoom_levels.begin(); j != host_zoom_levels.end(); ++j) { ZoomLevelChange change = {HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST, j->first, // host scheme, // scheme j->second // zoom level }; result.push_back(change); } } } return result; } void HostZoomMapImpl::SetZoomLevelForHost(const std::string& host, double level) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); { base::AutoLock auto_lock(lock_); if (ZoomValuesEqual(level, default_zoom_level_)) host_zoom_levels_.erase(host); else host_zoom_levels_[host] = level; } // TODO(wjmaclean) Should we use a GURL here? crbug.com/384486 SendZoomLevelChange(std::string(), host, level); HostZoomMap::ZoomLevelChange change; change.mode = HostZoomMap::ZOOM_CHANGED_FOR_HOST; change.host = host; change.zoom_level = level; zoom_level_changed_callbacks_.Notify(change); } void HostZoomMapImpl::SetZoomLevelForHostAndScheme(const std::string& scheme, const std::string& host, double level) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); { base::AutoLock auto_lock(lock_); scheme_host_zoom_levels_[scheme][host] = level; } SendZoomLevelChange(scheme, host, level); HostZoomMap::ZoomLevelChange change; change.mode = HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST; change.host = host; change.scheme = scheme; change.zoom_level = level; zoom_level_changed_callbacks_.Notify(change); } double HostZoomMapImpl::GetDefaultZoomLevel() const { return default_zoom_level_; } void HostZoomMapImpl::SetDefaultZoomLevel(double level) { default_zoom_level_ = level; } scoped_ptr<HostZoomMap::Subscription> HostZoomMapImpl::AddZoomLevelChangedCallback( const ZoomLevelChangedCallback& callback) { return zoom_level_changed_callbacks_.Add(callback); } double HostZoomMapImpl::GetZoomLevelForWebContents( const WebContentsImpl& web_contents_impl) const { int render_process_id = web_contents_impl.GetRenderProcessHost()->GetID(); int routing_id = web_contents_impl.GetRenderViewHost()->GetRoutingID(); if (UsesTemporaryZoomLevel(render_process_id, routing_id)) return GetTemporaryZoomLevel(render_process_id, routing_id); // Get the url from the navigation controller directly, as calling // WebContentsImpl::GetLastCommittedURL() may give us a virtual url that // is different than is stored in the map. GURL url; NavigationEntry* entry = web_contents_impl.GetController().GetLastCommittedEntry(); // It is possible for a WebContent's zoom level to be queried before // a navigation has occurred. if (entry) url = entry->GetURL(); return GetZoomLevelForHostAndScheme(url.scheme(), net::GetHostOrSpecFromURL(url)); } void HostZoomMapImpl::SetZoomLevelForWebContents( const WebContentsImpl& web_contents_impl, double level) { int render_process_id = web_contents_impl.GetRenderProcessHost()->GetID(); int render_view_id = web_contents_impl.GetRenderViewHost()->GetRoutingID(); if (UsesTemporaryZoomLevel(render_process_id, render_view_id)) { SetTemporaryZoomLevel(render_process_id, render_view_id, level); } else { // Get the url from the navigation controller directly, as calling // WebContentsImpl::GetLastCommittedURL() may give us a virtual url that // is different than what the render view is using. If the two don't match, // the attempt to set the zoom will fail. NavigationEntry* entry = web_contents_impl.GetController().GetLastCommittedEntry(); // Tests may invoke this function with a null entry, but we don't // want to save zoom levels in this case. if (!entry) return; GURL url = entry->GetURL(); SetZoomLevelForHost(net::GetHostOrSpecFromURL(url), level); } } void HostZoomMapImpl::SetZoomLevelForView(int render_process_id, int render_view_id, double level, const std::string& host) { if (UsesTemporaryZoomLevel(render_process_id, render_view_id)) SetTemporaryZoomLevel(render_process_id, render_view_id, level); else SetZoomLevelForHost(host, level); } bool HostZoomMapImpl::UsesTemporaryZoomLevel(int render_process_id, int render_view_id) const { RenderViewKey key(render_process_id, render_view_id); base::AutoLock auto_lock(lock_); return ContainsKey(temporary_zoom_levels_, key); } double HostZoomMapImpl::GetTemporaryZoomLevel(int render_process_id, int render_view_id) const { base::AutoLock auto_lock(lock_); RenderViewKey key(render_process_id, render_view_id); if (!ContainsKey(temporary_zoom_levels_, key)) return 0; return temporary_zoom_levels_.find(key)->second; } void HostZoomMapImpl::SetTemporaryZoomLevel(int render_process_id, int render_view_id, double level) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); { RenderViewKey key(render_process_id, render_view_id); base::AutoLock auto_lock(lock_); temporary_zoom_levels_[key] = level; } RenderViewHost* host = RenderViewHost::FromID(render_process_id, render_view_id); host->Send(new ViewMsg_SetZoomLevelForView(render_view_id, true, level)); HostZoomMap::ZoomLevelChange change; change.mode = HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM; change.host = GetHostFromProcessView(render_process_id, render_view_id); change.zoom_level = level; zoom_level_changed_callbacks_.Notify(change); } void HostZoomMapImpl::Observe(int type, const NotificationSource& source, const NotificationDetails& details) { switch (type) { case NOTIFICATION_RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW: { int render_view_id = Source<RenderViewHost>(source)->GetRoutingID(); int render_process_id = Source<RenderViewHost>(source)->GetProcess()->GetID(); ClearTemporaryZoomLevel(render_process_id, render_view_id); break; } default: NOTREACHED() << "Unexpected preference observed."; } } void HostZoomMapImpl::ClearTemporaryZoomLevel(int render_process_id, int render_view_id) { { base::AutoLock auto_lock(lock_); RenderViewKey key(render_process_id, render_view_id); TemporaryZoomLevels::iterator it = temporary_zoom_levels_.find(key); if (it == temporary_zoom_levels_.end()) return; temporary_zoom_levels_.erase(it); } RenderViewHost* host = RenderViewHost::FromID(render_process_id, render_view_id); DCHECK(host); // Send a new zoom level, host-specific if one exists. host->Send(new ViewMsg_SetZoomLevelForView( render_view_id, false, GetZoomLevelForHost( GetHostFromProcessView(render_process_id, render_view_id)))); } void HostZoomMapImpl::SendZoomLevelChange(const std::string& scheme, const std::string& host, double level) { for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator()); !i.IsAtEnd(); i.Advance()) { RenderProcessHost* render_process_host = i.GetCurrentValue(); if (HostZoomMap::GetDefaultForBrowserContext( render_process_host->GetBrowserContext()) == this) { render_process_host->Send( new ViewMsg_SetZoomLevelForCurrentURL(scheme, host, level)); } } } HostZoomMapImpl::~HostZoomMapImpl() { } } // namespace content