| OLD | NEW |
| (Empty) |
| 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 "components/ui/zoom/zoom_controller.h" | |
| 6 | |
| 7 #include "components/ui/zoom/zoom_event_manager.h" | |
| 8 #include "components/ui/zoom/zoom_observer.h" | |
| 9 #include "content/public/browser/host_zoom_map.h" | |
| 10 #include "content/public/browser/navigation_details.h" | |
| 11 #include "content/public/browser/navigation_entry.h" | |
| 12 #include "content/public/browser/render_process_host.h" | |
| 13 #include "content/public/browser/render_view_host.h" | |
| 14 #include "content/public/browser/web_contents.h" | |
| 15 #include "content/public/common/page_type.h" | |
| 16 #include "content/public/common/page_zoom.h" | |
| 17 #include "net/base/url_util.h" | |
| 18 | |
| 19 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ui_zoom::ZoomController); | |
| 20 | |
| 21 namespace ui_zoom { | |
| 22 | |
| 23 double ZoomController::GetZoomLevelForWebContents( | |
| 24 const content::WebContents* web_contents) { | |
| 25 if (!web_contents) | |
| 26 return 0.0; | |
| 27 | |
| 28 auto zoom_controller = FromWebContents(web_contents); | |
| 29 if (zoom_controller) | |
| 30 return zoom_controller->GetZoomLevel(); | |
| 31 | |
| 32 return content::HostZoomMap::GetZoomLevel(web_contents); | |
| 33 } | |
| 34 | |
| 35 ZoomController::ZoomController(content::WebContents* web_contents) | |
| 36 : content::WebContentsObserver(web_contents), | |
| 37 can_show_bubble_(true), | |
| 38 zoom_mode_(ZOOM_MODE_DEFAULT), | |
| 39 zoom_level_(1.0), | |
| 40 browser_context_(web_contents->GetBrowserContext()) { | |
| 41 host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents); | |
| 42 zoom_level_ = host_zoom_map_->GetDefaultZoomLevel(); | |
| 43 | |
| 44 zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback( | |
| 45 base::Bind(&ZoomController::OnZoomLevelChanged, base::Unretained(this))); | |
| 46 | |
| 47 UpdateState(std::string()); | |
| 48 } | |
| 49 | |
| 50 ZoomController::~ZoomController() { | |
| 51 } | |
| 52 | |
| 53 bool ZoomController::IsAtDefaultZoom() const { | |
| 54 return content::ZoomValuesEqual(GetZoomLevel(), GetDefaultZoomLevel()); | |
| 55 } | |
| 56 | |
| 57 ZoomController::RelativeZoom ZoomController::GetZoomRelativeToDefault() const { | |
| 58 double current_level = GetZoomLevel(); | |
| 59 double default_level = GetDefaultZoomLevel(); | |
| 60 if (content::ZoomValuesEqual(current_level, default_level)) | |
| 61 return ZOOM_AT_DEFAULT_ZOOM; | |
| 62 else if (current_level > default_level) | |
| 63 return ZOOM_ABOVE_DEFAULT_ZOOM; | |
| 64 return ZOOM_BELOW_DEFAULT_ZOOM; | |
| 65 } | |
| 66 | |
| 67 void ZoomController::AddObserver(ZoomObserver* observer) { | |
| 68 observers_.AddObserver(observer); | |
| 69 } | |
| 70 | |
| 71 void ZoomController::RemoveObserver(ZoomObserver* observer) { | |
| 72 observers_.RemoveObserver(observer); | |
| 73 } | |
| 74 | |
| 75 double ZoomController::GetZoomLevel() const { | |
| 76 return zoom_mode_ == ZOOM_MODE_MANUAL | |
| 77 ? zoom_level_ | |
| 78 : content::HostZoomMap::GetZoomLevel(web_contents()); | |
| 79 } | |
| 80 | |
| 81 int ZoomController::GetZoomPercent() const { | |
| 82 double zoom_factor = content::ZoomLevelToZoomFactor(GetZoomLevel()); | |
| 83 // Round double for return. | |
| 84 return static_cast<int>(zoom_factor * 100 + 0.5); | |
| 85 } | |
| 86 | |
| 87 bool ZoomController::SetZoomLevel(double zoom_level) { | |
| 88 // A client did not initiate this zoom change. | |
| 89 return SetZoomLevelByClient(zoom_level, NULL); | |
| 90 } | |
| 91 | |
| 92 bool ZoomController::SetZoomLevelByClient( | |
| 93 double zoom_level, | |
| 94 const scoped_refptr<const ZoomRequestClient>& client) { | |
| 95 content::NavigationEntry* entry = | |
| 96 web_contents()->GetController().GetLastCommittedEntry(); | |
| 97 // Cannot zoom in disabled mode. Also, don't allow changing zoom level on | |
| 98 // a crashed tab, an error page or an interstitial page. | |
| 99 if (zoom_mode_ == ZOOM_MODE_DISABLED || | |
| 100 !web_contents()->GetRenderViewHost()->IsRenderViewLive()) | |
| 101 return false; | |
| 102 | |
| 103 // Store client data so the |client| can be attributed when the zoom | |
| 104 // change completes. We expect that by the time this function returns that | |
| 105 // any observers that require this information will have requested it. | |
| 106 last_client_ = client; | |
| 107 | |
| 108 // Do not actually rescale the page in manual mode. | |
| 109 if (zoom_mode_ == ZOOM_MODE_MANUAL) { | |
| 110 // If the zoom level hasn't changed, early out to avoid sending an event. | |
| 111 if (content::ZoomValuesEqual(zoom_level_, zoom_level)) | |
| 112 return true; | |
| 113 | |
| 114 double old_zoom_level = zoom_level_; | |
| 115 zoom_level_ = zoom_level; | |
| 116 | |
| 117 // TODO(wjmaclean) Do we care about filling in host/scheme here? | |
| 118 content::HostZoomMap::ZoomLevelChange change; | |
| 119 change.mode = content::HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM; | |
| 120 change.zoom_level = zoom_level; | |
| 121 ZoomEventManager::GetForBrowserContext(browser_context_) | |
| 122 ->OnZoomLevelChanged(change); | |
| 123 | |
| 124 bool can_show_bubble = can_show_bubble_; | |
| 125 if (client && client->ShouldSuppressBubble()) | |
| 126 can_show_bubble = false; | |
| 127 | |
| 128 ZoomChangedEventData zoom_change_data(web_contents(), old_zoom_level, | |
| 129 zoom_level_, zoom_mode_, | |
| 130 can_show_bubble); | |
| 131 FOR_EACH_OBSERVER(ZoomObserver, observers_, | |
| 132 OnZoomChanged(zoom_change_data)); | |
| 133 | |
| 134 last_client_ = NULL; | |
| 135 return true; | |
| 136 } | |
| 137 | |
| 138 content::HostZoomMap* zoom_map = | |
| 139 content::HostZoomMap::GetForWebContents(web_contents()); | |
| 140 DCHECK(zoom_map); | |
| 141 DCHECK(!event_data_); | |
| 142 event_data_.reset(new ZoomChangedEventData(web_contents(), GetZoomLevel(), | |
| 143 zoom_level, zoom_mode_, | |
| 144 false /* can_show_bubble */)); | |
| 145 int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); | |
| 146 int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); | |
| 147 if (zoom_mode_ == ZOOM_MODE_ISOLATED || | |
| 148 zoom_map->UsesTemporaryZoomLevel(render_process_id, render_view_id)) { | |
| 149 zoom_map->SetTemporaryZoomLevel(render_process_id, render_view_id, | |
| 150 zoom_level); | |
| 151 } else { | |
| 152 if (!entry) { | |
| 153 last_client_ = NULL; | |
| 154 // If we exit without triggering an update, we should clear event_data_, | |
| 155 // else we may later trigger a DCHECK(event_data_). | |
| 156 event_data_.reset(); | |
| 157 return false; | |
| 158 } | |
| 159 std::string host = | |
| 160 net::GetHostOrSpecFromURL(content::HostZoomMap::GetURLFromEntry(entry)); | |
| 161 zoom_map->SetZoomLevelForHost(host, zoom_level); | |
| 162 } | |
| 163 | |
| 164 DCHECK(!event_data_); | |
| 165 last_client_ = NULL; | |
| 166 return true; | |
| 167 } | |
| 168 | |
| 169 void ZoomController::SetZoomMode(ZoomMode new_mode) { | |
| 170 if (new_mode == zoom_mode_) | |
| 171 return; | |
| 172 | |
| 173 content::HostZoomMap* zoom_map = | |
| 174 content::HostZoomMap::GetForWebContents(web_contents()); | |
| 175 DCHECK(zoom_map); | |
| 176 int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); | |
| 177 int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); | |
| 178 double original_zoom_level = GetZoomLevel(); | |
| 179 | |
| 180 DCHECK(!event_data_); | |
| 181 event_data_.reset(new ZoomChangedEventData( | |
| 182 web_contents(), original_zoom_level, original_zoom_level, new_mode, | |
| 183 new_mode != ZOOM_MODE_DEFAULT)); | |
| 184 | |
| 185 switch (new_mode) { | |
| 186 case ZOOM_MODE_DEFAULT: { | |
| 187 content::NavigationEntry* entry = | |
| 188 web_contents()->GetController().GetLastCommittedEntry(); | |
| 189 | |
| 190 if (entry) { | |
| 191 GURL url = content::HostZoomMap::GetURLFromEntry(entry); | |
| 192 std::string host = net::GetHostOrSpecFromURL(url); | |
| 193 | |
| 194 if (zoom_map->HasZoomLevel(url.scheme(), host)) { | |
| 195 // If there are other tabs with the same origin, then set this tab's | |
| 196 // zoom level to match theirs. The temporary zoom level will be | |
| 197 // cleared below, but this call will make sure this tab re-draws at | |
| 198 // the correct zoom level. | |
| 199 double origin_zoom_level = | |
| 200 zoom_map->GetZoomLevelForHostAndScheme(url.scheme(), host); | |
| 201 event_data_->new_zoom_level = origin_zoom_level; | |
| 202 zoom_map->SetTemporaryZoomLevel(render_process_id, render_view_id, | |
| 203 origin_zoom_level); | |
| 204 } else { | |
| 205 // The host will need a level prior to removing the temporary level. | |
| 206 // We don't want the zoom level to change just because we entered | |
| 207 // default mode. | |
| 208 zoom_map->SetZoomLevelForHost(host, original_zoom_level); | |
| 209 } | |
| 210 } | |
| 211 // Remove per-tab zoom data for this tab. No event callback expected. | |
| 212 zoom_map->ClearTemporaryZoomLevel(render_process_id, render_view_id); | |
| 213 break; | |
| 214 } | |
| 215 case ZOOM_MODE_ISOLATED: { | |
| 216 // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the | |
| 217 // page needs an initial isolated zoom back to the same level it was at | |
| 218 // in the other mode. | |
| 219 if (zoom_mode_ != ZOOM_MODE_DISABLED) { | |
| 220 zoom_map->SetTemporaryZoomLevel(render_process_id, render_view_id, | |
| 221 original_zoom_level); | |
| 222 } else { | |
| 223 // When we don't call any HostZoomMap set functions, we send the event | |
| 224 // manually. | |
| 225 FOR_EACH_OBSERVER(ZoomObserver, observers_, | |
| 226 OnZoomChanged(*event_data_)); | |
| 227 event_data_.reset(); | |
| 228 } | |
| 229 break; | |
| 230 } | |
| 231 case ZOOM_MODE_MANUAL: { | |
| 232 // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the | |
| 233 // page needs to be resized to the default zoom. While in manual mode, | |
| 234 // the zoom level is handled independently. | |
| 235 if (zoom_mode_ != ZOOM_MODE_DISABLED) { | |
| 236 zoom_map->SetTemporaryZoomLevel(render_process_id, render_view_id, | |
| 237 GetDefaultZoomLevel()); | |
| 238 zoom_level_ = original_zoom_level; | |
| 239 } else { | |
| 240 // When we don't call any HostZoomMap set functions, we send the event | |
| 241 // manually. | |
| 242 FOR_EACH_OBSERVER(ZoomObserver, observers_, | |
| 243 OnZoomChanged(*event_data_)); | |
| 244 event_data_.reset(); | |
| 245 } | |
| 246 break; | |
| 247 } | |
| 248 case ZOOM_MODE_DISABLED: { | |
| 249 // The page needs to be zoomed back to default before disabling the zoom | |
| 250 double new_zoom_level = GetDefaultZoomLevel(); | |
| 251 event_data_->new_zoom_level = new_zoom_level; | |
| 252 zoom_map->SetTemporaryZoomLevel(render_process_id, render_view_id, | |
| 253 new_zoom_level); | |
| 254 break; | |
| 255 } | |
| 256 } | |
| 257 // Any event data we've stored should have been consumed by this point. | |
| 258 DCHECK(!event_data_); | |
| 259 | |
| 260 zoom_mode_ = new_mode; | |
| 261 } | |
| 262 | |
| 263 void ZoomController::ResetZoomModeOnNavigationIfNeeded(const GURL& url) { | |
| 264 if (zoom_mode_ != ZOOM_MODE_ISOLATED && zoom_mode_ != ZOOM_MODE_MANUAL) | |
| 265 return; | |
| 266 | |
| 267 int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); | |
| 268 int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); | |
| 269 content::HostZoomMap* zoom_map = | |
| 270 content::HostZoomMap::GetForWebContents(web_contents()); | |
| 271 zoom_level_ = zoom_map->GetDefaultZoomLevel(); | |
| 272 double old_zoom_level = zoom_map->GetZoomLevel(web_contents()); | |
| 273 double new_zoom_level = zoom_map->GetZoomLevelForHostAndScheme( | |
| 274 url.scheme(), net::GetHostOrSpecFromURL(url)); | |
| 275 event_data_.reset(new ZoomChangedEventData( | |
| 276 web_contents(), old_zoom_level, new_zoom_level, ZOOM_MODE_DEFAULT, | |
| 277 false /* can_show_bubble */)); | |
| 278 // The call to ClearTemporaryZoomLevel() doesn't generate any events from | |
| 279 // HostZoomMap, but the call to UpdateState() at the end of this function | |
| 280 // will notify our observers. | |
| 281 // Note: it's possible the render_process/view ids have disappeared (e.g. | |
| 282 // if we navigated to a new origin), but this won't cause a problem in the | |
| 283 // call below. | |
| 284 zoom_map->ClearTemporaryZoomLevel(render_process_id, render_view_id); | |
| 285 zoom_mode_ = ZOOM_MODE_DEFAULT; | |
| 286 } | |
| 287 | |
| 288 void ZoomController::DidNavigateMainFrame( | |
| 289 const content::LoadCommittedDetails& details, | |
| 290 const content::FrameNavigateParams& params) { | |
| 291 if (details.entry && details.entry->GetPageType() == content::PAGE_TYPE_ERROR) | |
| 292 content::HostZoomMap::SendErrorPageZoomLevelRefresh(web_contents()); | |
| 293 | |
| 294 if (!details.is_in_page) | |
| 295 ResetZoomModeOnNavigationIfNeeded(params.url); | |
| 296 | |
| 297 // If the main frame's content has changed, the new page may have a different | |
| 298 // zoom level from the old one. | |
| 299 UpdateState(std::string()); | |
| 300 DCHECK(!event_data_); | |
| 301 } | |
| 302 | |
| 303 void ZoomController::WebContentsDestroyed() { | |
| 304 // At this point we should no longer be sending any zoom events with this | |
| 305 // WebContents. | |
| 306 observers_.Clear(); | |
| 307 } | |
| 308 | |
| 309 void ZoomController::RenderFrameHostChanged( | |
| 310 content::RenderFrameHost* old_host, | |
| 311 content::RenderFrameHost* new_host) { | |
| 312 // If our associated HostZoomMap changes, update our event subscription. | |
| 313 content::HostZoomMap* new_host_zoom_map = | |
| 314 content::HostZoomMap::GetForWebContents(web_contents()); | |
| 315 if (new_host_zoom_map == host_zoom_map_) | |
| 316 return; | |
| 317 | |
| 318 host_zoom_map_ = new_host_zoom_map; | |
| 319 zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback( | |
| 320 base::Bind(&ZoomController::OnZoomLevelChanged, base::Unretained(this))); | |
| 321 } | |
| 322 | |
| 323 void ZoomController::OnZoomLevelChanged( | |
| 324 const content::HostZoomMap::ZoomLevelChange& change) { | |
| 325 UpdateState(change.host); | |
| 326 } | |
| 327 | |
| 328 void ZoomController::UpdateState(const std::string& host) { | |
| 329 // If |host| is empty, all observers should be updated. | |
| 330 if (!host.empty()) { | |
| 331 // Use the navigation entry's URL instead of the WebContents' so virtual | |
| 332 // URLs work (e.g. chrome://settings). http://crbug.com/153950 | |
| 333 content::NavigationEntry* entry = | |
| 334 web_contents()->GetController().GetLastCommittedEntry(); | |
| 335 if (!entry || | |
| 336 host != net::GetHostOrSpecFromURL( | |
| 337 content::HostZoomMap::GetURLFromEntry(entry))) { | |
| 338 return; | |
| 339 } | |
| 340 } | |
| 341 | |
| 342 if (event_data_) { | |
| 343 // For state changes initiated within the ZoomController, information about | |
| 344 // the change should be sent. | |
| 345 ZoomChangedEventData zoom_change_data = *event_data_; | |
| 346 event_data_.reset(); | |
| 347 // The zoom bubble should not be shown for zoom changes where the host | |
| 348 // is empty. | |
| 349 zoom_change_data.can_show_bubble = can_show_bubble_ && !host.empty(); | |
| 350 FOR_EACH_OBSERVER(ZoomObserver, observers_, | |
| 351 OnZoomChanged(zoom_change_data)); | |
| 352 } else { | |
| 353 // TODO(wjmaclean) Should we consider having HostZoomMap send both old and | |
| 354 // new zoom levels here? | |
| 355 double zoom_level = GetZoomLevel(); | |
| 356 // We never show a zoom bubble for an event we didn't generate. | |
| 357 ZoomChangedEventData zoom_change_data(web_contents(), zoom_level, | |
| 358 zoom_level, zoom_mode_, | |
| 359 false /* can_show_bubble */); | |
| 360 FOR_EACH_OBSERVER(ZoomObserver, observers_, | |
| 361 OnZoomChanged(zoom_change_data)); | |
| 362 } | |
| 363 } | |
| 364 | |
| 365 void ZoomController::SetPageScaleFactorIsOneForTesting(bool is_one) { | |
| 366 int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); | |
| 367 int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); | |
| 368 host_zoom_map_->SetPageScaleFactorIsOneForView( | |
| 369 render_process_id, render_view_id, is_one); | |
| 370 } | |
| 371 | |
| 372 bool ZoomController::PageScaleFactorIsOne() const { | |
| 373 return content::HostZoomMap::PageScaleFactorIsOne(web_contents()); | |
| 374 } | |
| 375 | |
| 376 } // namespace ui_zoom | |
| OLD | NEW |