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 "chrome_frame/ready_mode/ready_mode.h" 6 7 #include <atlbase.h> 8 #include <shlguid.h> 9 10 #include "base/compiler_specific.h" 11 #include "base/logging.h" 12 #include "base/memory/linked_ptr.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/memory/weak_ptr.h" 15 #include "base/win/scoped_bstr.h" 16 #include "base/win/scoped_comptr.h" 17 #include "base/win/win_util.h" 18 #include "chrome/installer/util/browser_distribution.h" 19 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 20 #include "chrome_frame/infobars/infobar_manager.h" 21 #include "chrome_frame/ready_mode/internal/ready_mode_web_browser_adapter.h" 22 #include "chrome_frame/ready_mode/internal/ready_prompt_content.h" 23 #include "chrome_frame/ready_mode/internal/registry_ready_mode_state.h" 24 #include "chrome_frame/ready_mode/internal/url_launcher.h" 25 #include "chrome_frame/utils.h" 26 27 namespace { 28 29 // Temporarily disable Ready Mode for 36 hours when the user so indicates. 30 const int kTemporaryDeclineDurationMinutes = 60 * 36; 31 32 class BrowserObserver; 33 34 // A helper for BrowserObserver to observe the user's choice in the Ready Mode 35 // prompt. 36 class StateObserver : public RegistryReadyModeState::Observer { 37 public: 38 explicit StateObserver(const base::WeakPtr<BrowserObserver>& ready_mode_ui); 39 ~StateObserver(); 40 41 // RegistryReadyModeState::Observer implementation 42 virtual void OnStateChange(ReadyModeStatus status); 43 44 private: 45 base::WeakPtr<BrowserObserver> ready_mode_ui_; 46 47 DISALLOW_COPY_AND_ASSIGN(StateObserver); 48 }; // class StateObserver 49 50 // Manages the Ready Mode UI in response to browsing ChromeFrame- or Host- 51 // rendered pages. Shows the Ready Mode prompt when the user browses to a GCF- 52 // enabled page. Hides the prompt when the user begins navigating to a new 53 // domain or when they navigate to a new page in the same domain that is not 54 // GCF enabled. 55 // 56 // Uses InstallerAdapter and RegistryReadyMode to query and update the 57 // installation state. Uninstalls the ReadyModeWebBrowserAdapter when the user 58 // temporarily or permanently exits Ready Mode (decline or accept Chrome Frame). 59 // If the user declines Chrome Frame, the current page is reloaded in the Host 60 // renderer. 61 class BrowserObserver : public ReadyModeWebBrowserAdapter::Observer { 62 public: 63 BrowserObserver(ready_mode::Delegate* chrome_frame, 64 IWebBrowser2* web_browser, 65 ReadyModeWebBrowserAdapter* adapter); 66 67 // ReadyModeWebBrowserAdapter::Observer implementation 68 virtual void OnNavigateTo(const std::wstring& url); 69 virtual void OnRenderInChromeFrame(const std::wstring& url); 70 virtual void OnRenderInHost(const std::wstring& url); 71 72 private: 73 friend class StateObserver; 74 75 // Called by the StateObserver 76 void OnReadyModeDisabled(); 77 void OnReadyModeAccepted(); 78 79 // Helpers for showing infobar prompts 80 void ShowPrompt(); 81 void Hide(); 82 InfobarManager* GetInfobarManager(); 83 84 GURL rendered_url_; 85 linked_ptr<ready_mode::Delegate> chrome_frame_; 86 base::win::ScopedComPtr<IWebBrowser2> web_browser_; 87 // The adapter owns us, so we use a weak reference 88 ReadyModeWebBrowserAdapter* adapter_; 89 base::WeakPtrFactory<BrowserObserver> weak_ptr_factory_; 90 91 DISALLOW_COPY_AND_ASSIGN(BrowserObserver); 92 }; // class BrowserObserver 93 94 // Implements launching of a URL in an instance of IWebBrowser2. 95 class UrlLauncherImpl : public UrlLauncher { 96 public: 97 explicit UrlLauncherImpl(IWebBrowser2* web_browser); 98 99 // UrlLauncher implementation 100 void LaunchUrl(const std::wstring& url); 101 102 private: 103 base::win::ScopedComPtr<IWebBrowser2> web_browser_; 104 }; // class UrlLaucherImpl 105 106 UrlLauncherImpl::UrlLauncherImpl(IWebBrowser2* web_browser) { 107 DCHECK(web_browser); 108 web_browser_ = web_browser; 109 } 110 111 void UrlLauncherImpl::LaunchUrl(const std::wstring& url) { 112 VARIANT flags = { VT_I4 }; 113 V_I4(&flags) = navOpenInNewWindow; 114 base::win::ScopedBstr location(url.c_str()); 115 116 HRESULT hr = web_browser_->Navigate(location, &flags, NULL, NULL, NULL); 117 DLOG_IF(ERROR, FAILED(hr)) << "Failed to invoke Navigate on IWebBrowser2. " 118 << "Error: " << hr; 119 } 120 121 StateObserver::StateObserver( 122 const base::WeakPtr<BrowserObserver>& ready_mode_ui) 123 : ready_mode_ui_(ready_mode_ui) { 124 } 125 126 StateObserver::~StateObserver() { 127 } 128 129 void StateObserver::OnStateChange(ReadyModeStatus status) { 130 if (ready_mode_ui_ == NULL) 131 return; 132 133 switch (status) { 134 case READY_MODE_PERMANENTLY_DECLINED: 135 case READY_MODE_TEMPORARILY_DECLINED: 136 ready_mode_ui_->OnReadyModeDisabled(); 137 break; 138 139 case READY_MODE_ACCEPTED: 140 ready_mode_ui_->OnReadyModeAccepted(); 141 break; 142 143 case READY_MODE_ACTIVE: 144 break; 145 146 default: 147 NOTREACHED(); 148 break; 149 } 150 } 151 152 BrowserObserver::BrowserObserver(ready_mode::Delegate* chrome_frame, 153 IWebBrowser2* web_browser, 154 ReadyModeWebBrowserAdapter* adapter) 155 : web_browser_(web_browser), 156 chrome_frame_(chrome_frame), 157 adapter_(adapter), 158 weak_ptr_factory_(this) { 159 } 160 161 void BrowserObserver::OnNavigateTo(const std::wstring& url) { 162 if (!net::registry_controlled_domains::SameDomainOrHost( 163 GURL(url), 164 rendered_url_, 165 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)) { 166 rendered_url_ = GURL(); 167 Hide(); 168 } 169 } 170 171 void BrowserObserver::OnRenderInChromeFrame(const std::wstring& url) { 172 ShowPrompt(); 173 rendered_url_ = GURL(url); 174 } 175 176 void BrowserObserver::OnRenderInHost(const std::wstring& url) { 177 Hide(); 178 rendered_url_ = GURL(url); 179 } 180 181 void BrowserObserver::OnReadyModeDisabled() { 182 // We don't hold a reference to the adapter, since it owns us (in order to 183 // break circular dependency). But we should still AddRef it before 184 // invocation. 185 base::win::ScopedComPtr<ReadyModeWebBrowserAdapter, NULL> reference(adapter_); 186 187 // adapter_->Uninitialize may delete us, so we should not refer to members 188 // after that point. 189 base::win::ScopedComPtr<IWebBrowser2> web_browser(web_browser_); 190 191 chrome_frame_->DisableChromeFrame(); 192 adapter_->Uninitialize(); 193 194 VARIANT flags = { VT_I4 }; 195 V_I4(&flags) = navNoHistory; 196 base::win::ScopedBstr location; 197 198 HRESULT hr = web_browser->get_LocationURL(location.Receive()); 199 DLOG_IF(ERROR, FAILED(hr)) << "Failed to get current location from " 200 << "IWebBrowser2. Error: " << hr; 201 202 if (SUCCEEDED(hr)) { 203 hr = web_browser->Navigate(location, &flags, NULL, NULL, NULL); 204 DLOG_IF(ERROR, FAILED(hr)) << "Failed to invoke Navigate on IWebBrowser2. " 205 << "Error: " << hr; 206 } 207 } 208 209 void BrowserObserver::OnReadyModeAccepted() { 210 // See comment in OnReadyModeDisabled. 211 base::win::ScopedComPtr<ReadyModeWebBrowserAdapter, NULL> reference(adapter_); 212 adapter_->Uninitialize(); 213 } 214 215 void BrowserObserver::ShowPrompt() { 216 // This pointer is self-managed and not guaranteed to survive handling of 217 // Windows events. 218 InfobarManager* infobar_manager = GetInfobarManager(); 219 220 if (infobar_manager) { 221 // Owned by ready_mode_state 222 scoped_ptr<RegistryReadyModeState::Observer> ready_mode_state_observer( 223 new StateObserver(weak_ptr_factory_.GetWeakPtr())); 224 225 BrowserDistribution* dist = 226 BrowserDistribution::GetSpecificDistribution( 227 BrowserDistribution::CHROME_BINARIES); 228 229 // Owned by infobar_content 230 scoped_ptr<ReadyModeState> ready_mode_state(new RegistryReadyModeState( 231 dist->GetStateKey(), 232 base::TimeDelta::FromMinutes(kTemporaryDeclineDurationMinutes), 233 ready_mode_state_observer.release())); 234 235 // Owned by infobar_content 236 scoped_ptr<UrlLauncher> url_launcher(new UrlLauncherImpl(web_browser_)); 237 238 // Owned by infobar_manager 239 scoped_ptr<InfobarContent> infobar_content(new ReadyPromptContent( 240 ready_mode_state.release(), url_launcher.release())); 241 242 infobar_manager->Show(infobar_content.release(), TOP_INFOBAR); 243 } 244 } 245 246 void BrowserObserver::Hide() { 247 InfobarManager* infobar_manager = GetInfobarManager(); 248 if (infobar_manager) 249 infobar_manager->HideAll(); 250 } 251 252 InfobarManager* BrowserObserver::GetInfobarManager() { 253 HRESULT hr = NOERROR; 254 255 base::win::ScopedComPtr<IOleWindow> ole_window; 256 hr = DoQueryService(SID_SShellBrowser, web_browser_, ole_window.Receive()); 257 if (FAILED(hr) || ole_window == NULL) { 258 DLOG(ERROR) << "Failed to query SID_SShellBrowser from IWebBrowser2. " 259 << "Error: " << hr; 260 return NULL; 261 } 262 263 HWND web_browserhwnd = NULL; 264 hr = ole_window->GetWindow(&web_browserhwnd); 265 if (FAILED(hr) || web_browserhwnd == NULL) { 266 DLOG(ERROR) << "Failed to query HWND from IOleWindow. " 267 << "Error: " << hr; 268 return NULL; 269 } 270 271 return InfobarManager::Get(web_browserhwnd); 272 } 273 274 // Wraps an existing Delegate so that ownership may be shared. 275 class DelegateWrapper : public ready_mode::Delegate { 276 public: 277 explicit DelegateWrapper(linked_ptr<ready_mode::Delegate> wrapped); 278 279 // ready_mode::Delegate implementation 280 virtual void DisableChromeFrame(); 281 282 private: 283 linked_ptr<ready_mode::Delegate> wrapped_; 284 285 DISALLOW_COPY_AND_ASSIGN(DelegateWrapper); 286 }; // class DelegateWrapper 287 288 DelegateWrapper::DelegateWrapper(linked_ptr<ready_mode::Delegate> wrapped) 289 : wrapped_(wrapped) { 290 } 291 292 void DelegateWrapper::DisableChromeFrame() { 293 wrapped_->DisableChromeFrame(); 294 } 295 296 // Attempts to create a ReadyModeWebBrowserAdapter instance. 297 bool CreateWebBrowserAdapter(ReadyModeWebBrowserAdapter** pointer) { 298 *pointer = NULL; 299 300 CComObject<ReadyModeWebBrowserAdapter>* com_object; 301 HRESULT hr = 302 CComObject<ReadyModeWebBrowserAdapter>::CreateInstance(&com_object); 303 304 if (FAILED(hr)) { 305 DLOG(ERROR) << "Failed to create instance of ReadyModeWebBrowserAdapter. " 306 << "Error: " << hr; 307 return false; 308 } 309 310 com_object->AddRef(); 311 *pointer = com_object; 312 return true; 313 } 314 315 // Attempts to install Ready Mode prompts in the provided web browser. Will 316 // notify the provided Delegate if the user declines Chrome Frame temporarily or 317 // permanently. 318 bool InstallPrompts(linked_ptr<ready_mode::Delegate> delegate, 319 IWebBrowser2* web_browser) { 320 base::win::ScopedComPtr<ReadyModeWebBrowserAdapter, NULL> adapter; 321 322 if (!CreateWebBrowserAdapter(adapter.Receive())) 323 return false; 324 325 // Wrap the original delegate so that we can share it with the 326 // ReadyModeWebBrowserAdapter 327 scoped_ptr<DelegateWrapper> delegate_wrapper(new DelegateWrapper(delegate)); 328 329 // Pass ownership of our delegate to the BrowserObserver 330 scoped_ptr<ReadyModeWebBrowserAdapter::Observer> browser_observer( 331 new BrowserObserver(delegate_wrapper.release(), web_browser, adapter)); 332 333 // Owns the BrowserObserver 334 return adapter->Initialize(web_browser, browser_observer.release()); 335 } 336 337 // Checks if the provided status implies disabling Chrome Frame functionality. 338 bool ShouldDisableChromeFrame(ReadyModeStatus status) { 339 switch (status) { 340 case READY_MODE_PERMANENTLY_DECLINED: 341 case READY_MODE_TEMPORARILY_DECLINED: 342 case READY_MODE_TEMPORARY_DECLINE_EXPIRED: 343 return true; 344 345 case READY_MODE_ACCEPTED: 346 case READY_MODE_ACTIVE: 347 return false; 348 349 default: 350 NOTREACHED(); 351 return true; 352 } 353 } 354 355 } // namespace 356 357 namespace ready_mode { 358 359 // Determines the current Ready Mode state. If it is active, attempts to set up 360 // prompting. If we cannot set up prompting, attempts to temporarily disable 361 // Ready Mode. In the end, if Ready Mode is disabled, pass that information on 362 // to the Delegate, so that it may disabled Chrome Frame functionality. 363 void Configure(Delegate* chrome_frame, IWebBrowser2* web_browser) { 364 // Take ownership of the delegate 365 linked_ptr<Delegate> delegate(chrome_frame); 366 chrome_frame = NULL; 367 BrowserDistribution* dist = 368 BrowserDistribution::GetSpecificDistribution( 369 BrowserDistribution::CHROME_BINARIES); 370 371 RegistryReadyModeState ready_mode_state( 372 dist->GetStateKey(), 373 base::TimeDelta::FromMinutes(kTemporaryDeclineDurationMinutes), 374 NULL); // NULL => no observer required 375 376 ReadyModeStatus status = ready_mode_state.GetStatus(); 377 378 // If the user temporarily declined Chrome Frame, but the timeout has elapsed, 379 // attempt to revert to active Ready Mode state. 380 if (status == READY_MODE_TEMPORARY_DECLINE_EXPIRED) { 381 ready_mode_state.ExpireTemporaryDecline(); 382 status = ready_mode_state.GetStatus(); 383 } 384 385 // If Ready Mode is active, attempt to set up prompting. 386 if (status == READY_MODE_ACTIVE) { 387 if (!InstallPrompts(delegate, web_browser)) { 388 // Failed to set up prompting. Turn off Ready Mode for now. 389 ready_mode_state.TemporarilyDeclineChromeFrame(); 390 status = ready_mode_state.GetStatus(); 391 } 392 } 393 394 // Depending on the state we finally end up in, tell our Delegate to disable 395 // Chrome Frame functionality. 396 if (ShouldDisableChromeFrame(status)) 397 delegate->DisableChromeFrame(); 398 } 399 400 } // namespace ready_mode 401