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/browser/extensions/extension_host.h" 6 7#include <list> 8 9#include "base/bind.h" 10#include "base/logging.h" 11#include "base/memory/singleton.h" 12#include "base/memory/weak_ptr.h" 13#include "base/message_loop/message_loop.h" 14#include "base/metrics/histogram.h" 15#include "base/strings/string_util.h" 16#include "base/strings/utf_string_conversions.h" 17#include "chrome/browser/chrome_notification_types.h" 18#include "chrome/browser/extensions/error_console/error_console.h" 19#include "chrome/browser/extensions/extension_service.h" 20#include "chrome/browser/extensions/extension_system.h" 21#include "chrome/browser/extensions/extension_tab_util.h" 22#include "chrome/browser/extensions/extension_web_contents_observer.h" 23#include "chrome/browser/media/media_capture_devices_dispatcher.h" 24#include "chrome/browser/ui/prefs/prefs_tab_helper.h" 25#include "chrome/common/chrome_constants.h" 26#include "chrome/common/extensions/extension_constants.h" 27#include "chrome/common/extensions/extension_messages.h" 28#include "chrome/common/render_messages.h" 29#include "chrome/common/url_constants.h" 30#include "content/public/browser/browser_context.h" 31#include "content/public/browser/content_browser_client.h" 32#include "content/public/browser/native_web_keyboard_event.h" 33#include "content/public/browser/notification_service.h" 34#include "content/public/browser/render_process_host.h" 35#include "content/public/browser/render_view_host.h" 36#include "content/public/browser/render_widget_host_view.h" 37#include "content/public/browser/site_instance.h" 38#include "content/public/browser/web_contents.h" 39#include "extensions/browser/event_router.h" 40#include "extensions/browser/extension_error.h" 41#include "extensions/browser/extensions_browser_client.h" 42#include "extensions/browser/process_manager.h" 43#include "extensions/browser/view_type_utils.h" 44#include "extensions/common/extension.h" 45#include "extensions/common/extension_urls.h" 46#include "extensions/common/feature_switch.h" 47#include "extensions/common/manifest_handlers/background_info.h" 48#include "ui/base/l10n/l10n_util.h" 49#include "ui/base/window_open_disposition.h" 50 51using content::BrowserContext; 52using content::OpenURLParams; 53using content::RenderProcessHost; 54using content::RenderViewHost; 55using content::SiteInstance; 56using content::WebContents; 57 58namespace extensions { 59 60// Helper class that rate-limits the creation of renderer processes for 61// ExtensionHosts, to avoid blocking the UI. 62class ExtensionHost::ProcessCreationQueue { 63 public: 64 static ProcessCreationQueue* GetInstance() { 65 return Singleton<ProcessCreationQueue>::get(); 66 } 67 68 // Add a host to the queue for RenderView creation. 69 void CreateSoon(ExtensionHost* host) { 70 queue_.push_back(host); 71 PostTask(); 72 } 73 74 // Remove a host from the queue (in case it's being deleted). 75 void Remove(ExtensionHost* host) { 76 Queue::iterator it = std::find(queue_.begin(), queue_.end(), host); 77 if (it != queue_.end()) 78 queue_.erase(it); 79 } 80 81 private: 82 friend class Singleton<ProcessCreationQueue>; 83 friend struct DefaultSingletonTraits<ProcessCreationQueue>; 84 ProcessCreationQueue() 85 : pending_create_(false), 86 ptr_factory_(this) {} 87 88 // Queue up a delayed task to process the next ExtensionHost in the queue. 89 void PostTask() { 90 if (!pending_create_) { 91 base::MessageLoop::current()->PostTask(FROM_HERE, 92 base::Bind(&ProcessCreationQueue::ProcessOneHost, 93 ptr_factory_.GetWeakPtr())); 94 pending_create_ = true; 95 } 96 } 97 98 // Create the RenderView for the next host in the queue. 99 void ProcessOneHost() { 100 pending_create_ = false; 101 if (queue_.empty()) 102 return; // can happen on shutdown 103 104 queue_.front()->CreateRenderViewNow(); 105 queue_.pop_front(); 106 107 if (!queue_.empty()) 108 PostTask(); 109 } 110 111 typedef std::list<ExtensionHost*> Queue; 112 Queue queue_; 113 bool pending_create_; 114 base::WeakPtrFactory<ProcessCreationQueue> ptr_factory_; 115}; 116 117//////////////// 118// ExtensionHost 119 120ExtensionHost::ExtensionHost(const Extension* extension, 121 SiteInstance* site_instance, 122 const GURL& url, 123 ViewType host_type) 124 : extension_(extension), 125 extension_id_(extension->id()), 126 browser_context_(site_instance->GetBrowserContext()), 127 render_view_host_(NULL), 128 did_stop_loading_(false), 129 document_element_available_(false), 130 initial_url_(url), 131 extension_function_dispatcher_(browser_context_, this), 132 extension_host_type_(host_type) { 133 // Not used for panels, see PanelHost. 134 DCHECK(host_type == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE || 135 host_type == VIEW_TYPE_EXTENSION_DIALOG || 136 host_type == VIEW_TYPE_EXTENSION_INFOBAR || 137 host_type == VIEW_TYPE_EXTENSION_POPUP); 138 host_contents_.reset(WebContents::Create( 139 WebContents::CreateParams(browser_context_, site_instance))), 140 content::WebContentsObserver::Observe(host_contents_.get()); 141 host_contents_->SetDelegate(this); 142 SetViewType(host_contents_.get(), host_type); 143 144 ExtensionWebContentsObserver::CreateForWebContents(host_contents()); 145 PrefsTabHelper::CreateForWebContents(host_contents()); 146 147 render_view_host_ = host_contents_->GetRenderViewHost(); 148 149 // Listen for when an extension is unloaded from the same profile, as it may 150 // be the same extension that this points to. 151 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 152 content::Source<BrowserContext>(browser_context_)); 153} 154 155ExtensionHost::~ExtensionHost() { 156 if (extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE && 157 extension_ && BackgroundInfo::HasLazyBackgroundPage(extension_)) { 158 UMA_HISTOGRAM_LONG_TIMES("Extensions.EventPageActiveTime", 159 since_created_.Elapsed()); 160 } 161 content::NotificationService::current()->Notify( 162 chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, 163 content::Source<BrowserContext>(browser_context_), 164 content::Details<ExtensionHost>(this)); 165 ProcessCreationQueue::GetInstance()->Remove(this); 166} 167 168content::RenderProcessHost* ExtensionHost::render_process_host() const { 169 return render_view_host()->GetProcess(); 170} 171 172RenderViewHost* ExtensionHost::render_view_host() const { 173 // TODO(mpcomplete): This can be NULL. How do we handle that? 174 return render_view_host_; 175} 176 177bool ExtensionHost::IsRenderViewLive() const { 178 return render_view_host()->IsRenderViewLive(); 179} 180 181void ExtensionHost::CreateRenderViewSoon() { 182 if ((render_process_host() && render_process_host()->HasConnection())) { 183 // If the process is already started, go ahead and initialize the RenderView 184 // synchronously. The process creation is the real meaty part that we want 185 // to defer. 186 CreateRenderViewNow(); 187 } else { 188 ProcessCreationQueue::GetInstance()->CreateSoon(this); 189 } 190} 191 192void ExtensionHost::CreateRenderViewNow() { 193 LoadInitialURL(); 194 if (IsBackgroundPage()) { 195 DCHECK(IsRenderViewLive()); 196 ExtensionSystem::GetForBrowserContext(browser_context_)-> 197 extension_service()->DidCreateRenderViewForBackgroundPage(this); 198 } 199} 200 201const GURL& ExtensionHost::GetURL() const { 202 return host_contents()->GetURL(); 203} 204 205void ExtensionHost::LoadInitialURL() { 206 host_contents_->GetController().LoadURL( 207 initial_url_, content::Referrer(), content::PAGE_TRANSITION_LINK, 208 std::string()); 209} 210 211bool ExtensionHost::IsBackgroundPage() const { 212 DCHECK(extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); 213 return true; 214} 215 216void ExtensionHost::Close() { 217 content::NotificationService::current()->Notify( 218 chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 219 content::Source<BrowserContext>(browser_context_), 220 content::Details<ExtensionHost>(this)); 221} 222 223void ExtensionHost::Observe(int type, 224 const content::NotificationSource& source, 225 const content::NotificationDetails& details) { 226 switch (type) { 227 case chrome::NOTIFICATION_EXTENSION_UNLOADED: 228 // The extension object will be deleted after this notification has been 229 // sent. NULL it out so that dirty pointer issues don't arise in cases 230 // when multiple ExtensionHost objects pointing to the same Extension are 231 // present. 232 if (extension_ == content::Details<UnloadedExtensionInfo>(details)-> 233 extension) { 234 extension_ = NULL; 235 } 236 break; 237 default: 238 NOTREACHED() << "Unexpected notification sent."; 239 break; 240 } 241} 242 243void ExtensionHost::RenderProcessGone(base::TerminationStatus status) { 244 // During browser shutdown, we may use sudden termination on an extension 245 // process, so it is expected to lose our connection to the render view. 246 // Do nothing. 247 RenderProcessHost* process_host = host_contents_->GetRenderProcessHost(); 248 if (process_host && process_host->FastShutdownStarted()) 249 return; 250 251 // In certain cases, multiple ExtensionHost objects may have pointed to 252 // the same Extension at some point (one with a background page and a 253 // popup, for example). When the first ExtensionHost goes away, the extension 254 // is unloaded, and any other host that pointed to that extension will have 255 // its pointer to it NULLed out so that any attempt to unload a dirty pointer 256 // will be averted. 257 if (!extension_) 258 return; 259 260 // TODO(aa): This is suspicious. There can be multiple views in an extension, 261 // and they aren't all going to use ExtensionHost. This should be in someplace 262 // more central, like EPM maybe. 263 content::NotificationService::current()->Notify( 264 chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED, 265 content::Source<BrowserContext>(browser_context_), 266 content::Details<ExtensionHost>(this)); 267} 268 269void ExtensionHost::DidStopLoading(content::RenderViewHost* render_view_host) { 270 bool notify = !did_stop_loading_; 271 did_stop_loading_ = true; 272 OnDidStopLoading(); 273 if (notify) { 274 if (extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { 275 if (extension_ && BackgroundInfo::HasLazyBackgroundPage(extension_)) { 276 UMA_HISTOGRAM_TIMES("Extensions.EventPageLoadTime", 277 since_created_.Elapsed()); 278 } else { 279 UMA_HISTOGRAM_TIMES("Extensions.BackgroundPageLoadTime", 280 since_created_.Elapsed()); 281 } 282 } else if (extension_host_type_ == VIEW_TYPE_EXTENSION_DIALOG) { 283 UMA_HISTOGRAM_TIMES("Extensions.DialogLoadTime", 284 since_created_.Elapsed()); 285 } else if (extension_host_type_ == VIEW_TYPE_EXTENSION_POPUP) { 286 UMA_HISTOGRAM_TIMES("Extensions.PopupLoadTime", 287 since_created_.Elapsed()); 288 } else if (extension_host_type_ == VIEW_TYPE_EXTENSION_INFOBAR) { 289 UMA_HISTOGRAM_TIMES("Extensions.InfobarLoadTime", 290 since_created_.Elapsed()); 291 } 292 293 // Send the notification last, because it might result in this being 294 // deleted. 295 content::NotificationService::current()->Notify( 296 chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING, 297 content::Source<BrowserContext>(browser_context_), 298 content::Details<ExtensionHost>(this)); 299 } 300} 301 302void ExtensionHost::OnDidStopLoading() { 303 DCHECK(extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); 304 // Nothing to do for background pages. 305} 306 307void ExtensionHost::DocumentAvailableInMainFrame() { 308 // If the document has already been marked as available for this host, then 309 // bail. No need for the redundant setup. http://crbug.com/31170 310 if (document_element_available_) 311 return; 312 document_element_available_ = true; 313 OnDocumentAvailable(); 314} 315 316void ExtensionHost::OnDocumentAvailable() { 317 DCHECK(extension_host_type_ == VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); 318 ExtensionSystem::GetForBrowserContext(browser_context_)-> 319 extension_service()->SetBackgroundPageReady(extension_); 320} 321 322void ExtensionHost::CloseContents(WebContents* contents) { 323 Close(); 324} 325 326bool ExtensionHost::OnMessageReceived(const IPC::Message& message) { 327 bool handled = true; 328 IPC_BEGIN_MESSAGE_MAP(ExtensionHost, message) 329 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest) 330 IPC_MESSAGE_HANDLER(ExtensionHostMsg_EventAck, OnEventAck) 331 IPC_MESSAGE_HANDLER(ExtensionHostMsg_IncrementLazyKeepaliveCount, 332 OnIncrementLazyKeepaliveCount) 333 IPC_MESSAGE_HANDLER(ExtensionHostMsg_DecrementLazyKeepaliveCount, 334 OnDecrementLazyKeepaliveCount) 335 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded, 336 OnDetailedConsoleMessageAdded) 337 IPC_MESSAGE_UNHANDLED(handled = false) 338 IPC_END_MESSAGE_MAP() 339 return handled; 340} 341 342void ExtensionHost::OnRequest(const ExtensionHostMsg_Request_Params& params) { 343 extension_function_dispatcher_.Dispatch(params, render_view_host()); 344} 345 346void ExtensionHost::OnEventAck() { 347 EventRouter* router = 348 ExtensionSystem::GetForBrowserContext(browser_context_)->event_router(); 349 if (router) 350 router->OnEventAck(browser_context_, extension_id()); 351} 352 353void ExtensionHost::OnIncrementLazyKeepaliveCount() { 354 ProcessManager* pm = ExtensionSystem::GetForBrowserContext( 355 browser_context_)->process_manager(); 356 if (pm) 357 pm->IncrementLazyKeepaliveCount(extension()); 358} 359 360void ExtensionHost::OnDecrementLazyKeepaliveCount() { 361 ProcessManager* pm = ExtensionSystem::GetForBrowserContext( 362 browser_context_)->process_manager(); 363 if (pm) 364 pm->DecrementLazyKeepaliveCount(extension()); 365} 366 367void ExtensionHost::OnDetailedConsoleMessageAdded( 368 const base::string16& message, 369 const base::string16& source, 370 const StackTrace& stack_trace, 371 int32 severity_level) { 372 if (!IsSourceFromAnExtension(source)) 373 return; 374 375 GURL context_url; 376 WebContents* associated_contents = GetAssociatedWebContents(); 377 if (associated_contents) 378 context_url = associated_contents->GetLastCommittedURL(); 379 else if (host_contents_.get()) 380 context_url = host_contents_->GetLastCommittedURL(); 381 382 ErrorConsole* console = 383 ExtensionSystem::GetForBrowserContext(browser_context_)->error_console(); 384 if (!console) 385 return; 386 387 console->ReportError( 388 scoped_ptr<ExtensionError>(new RuntimeError( 389 extension_id_, 390 browser_context_->IsOffTheRecord(), 391 source, 392 message, 393 stack_trace, 394 context_url, 395 static_cast<logging::LogSeverity>(severity_level), 396 render_view_host_->GetRoutingID(), 397 render_view_host_->GetProcess()->GetID()))); 398} 399 400// content::WebContentsObserver 401 402void ExtensionHost::RenderViewCreated(RenderViewHost* render_view_host) { 403 render_view_host_ = render_view_host; 404} 405 406void ExtensionHost::RenderViewDeleted(RenderViewHost* render_view_host) { 407 // If our RenderViewHost is deleted, fall back to the host_contents' current 408 // RVH. There is sometimes a small gap between the pending RVH being deleted 409 // and RenderViewCreated being called, so we update it here. 410 if (render_view_host == render_view_host_) 411 render_view_host_ = host_contents_->GetRenderViewHost(); 412} 413 414content::JavaScriptDialogManager* ExtensionHost::GetJavaScriptDialogManager() { 415 return ExtensionsBrowserClient::Get()->GetJavaScriptDialogManager(); 416} 417 418void ExtensionHost::AddNewContents(WebContents* source, 419 WebContents* new_contents, 420 WindowOpenDisposition disposition, 421 const gfx::Rect& initial_pos, 422 bool user_gesture, 423 bool* was_blocked) { 424 // First, if the creating extension view was associated with a tab contents, 425 // use that tab content's delegate. We must be careful here that the 426 // associated tab contents has the same profile as the new tab contents. In 427 // the case of extensions in 'spanning' incognito mode, they can mismatch. 428 // We don't want to end up putting a normal tab into an incognito window, or 429 // vice versa. 430 // Note that we don't do this for popup windows, because we need to associate 431 // those with their extension_app_id. 432 if (disposition != NEW_POPUP) { 433 WebContents* associated_contents = GetAssociatedWebContents(); 434 if (associated_contents && 435 associated_contents->GetBrowserContext() == 436 new_contents->GetBrowserContext()) { 437 WebContentsDelegate* delegate = associated_contents->GetDelegate(); 438 if (delegate) { 439 delegate->AddNewContents( 440 associated_contents, new_contents, disposition, initial_pos, 441 user_gesture, was_blocked); 442 return; 443 } 444 } 445 } 446 447 ExtensionTabUtil::CreateTab(new_contents, extension_id_, disposition, 448 initial_pos, user_gesture); 449} 450 451void ExtensionHost::RenderViewReady() { 452 content::NotificationService::current()->Notify( 453 chrome::NOTIFICATION_EXTENSION_HOST_CREATED, 454 content::Source<BrowserContext>(browser_context_), 455 content::Details<ExtensionHost>(this)); 456} 457 458void ExtensionHost::RequestMediaAccessPermission( 459 content::WebContents* web_contents, 460 const content::MediaStreamRequest& request, 461 const content::MediaResponseCallback& callback) { 462 MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest( 463 web_contents, request, callback, extension()); 464} 465 466} // namespace extensions 467