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