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