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 "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h" 6 7#include <set> 8 9#include "base/command_line.h" 10#include "chrome/browser/browser_process.h" 11#include "chrome/browser/extensions/browser_permissions_policy_delegate.h" 12#include "chrome/browser/extensions/extension_service.h" 13#include "chrome/browser/extensions/extension_web_ui.h" 14#include "chrome/browser/extensions/extension_webkit_preferences.h" 15#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h" 16#include "chrome/browser/profiles/profile.h" 17#include "chrome/browser/profiles/profile_io_data.h" 18#include "chrome/browser/profiles/profile_manager.h" 19#include "chrome/browser/renderer_host/chrome_extension_message_filter.h" 20#include "chrome/browser/sync_file_system/local/sync_file_system_backend.h" 21#include "chrome/common/chrome_constants.h" 22#include "chrome/common/extensions/extension_process_policy.h" 23#include "chrome/common/extensions/manifest_handlers/app_isolation_info.h" 24#include "content/public/browser/browser_thread.h" 25#include "content/public/browser/browser_url_handler.h" 26#include "content/public/browser/render_process_host.h" 27#include "content/public/browser/render_view_host.h" 28#include "content/public/browser/site_instance.h" 29#include "content/public/browser/web_contents.h" 30#include "content/public/common/content_switches.h" 31#include "extensions/browser/api/web_request/web_request_api.h" 32#include "extensions/browser/api/web_request/web_request_api_helpers.h" 33#include "extensions/browser/extension_host.h" 34#include "extensions/browser/extension_message_filter.h" 35#include "extensions/browser/extension_registry.h" 36#include "extensions/browser/extension_system.h" 37#include "extensions/browser/info_map.h" 38#include "extensions/browser/view_type_utils.h" 39#include "extensions/common/constants.h" 40#include "extensions/common/manifest_handlers/background_info.h" 41#include "extensions/common/manifest_handlers/web_accessible_resources_info.h" 42#include "extensions/common/switches.h" 43 44using content::BrowserThread; 45using content::BrowserURLHandler; 46using content::RenderViewHost; 47using content::SiteInstance; 48using content::WebContents; 49using content::WebPreferences; 50 51namespace extensions { 52 53namespace { 54 55// Used by the GetPrivilegeRequiredByUrl() and GetProcessPrivilege() functions 56// below. Extension, and isolated apps require different privileges to be 57// granted to their RenderProcessHosts. This classification allows us to make 58// sure URLs are served by hosts with the right set of privileges. 59enum RenderProcessHostPrivilege { 60 PRIV_NORMAL, 61 PRIV_HOSTED, 62 PRIV_ISOLATED, 63 PRIV_EXTENSION, 64}; 65 66RenderProcessHostPrivilege GetPrivilegeRequiredByUrl( 67 const GURL& url, 68 ExtensionService* service) { 69 // Default to a normal renderer cause it is lower privileged. This should only 70 // occur if the URL on a site instance is either malformed, or uninitialized. 71 // If it is malformed, then there is no need for better privileges anyways. 72 // If it is uninitialized, but eventually settles on being an a scheme other 73 // than normal webrenderer, the navigation logic will correct us out of band 74 // anyways. 75 if (!url.is_valid()) 76 return PRIV_NORMAL; 77 78 if (!url.SchemeIs(kExtensionScheme)) 79 return PRIV_NORMAL; 80 81 const Extension* extension = service->extensions()->GetByID(url.host()); 82 if (extension && AppIsolationInfo::HasIsolatedStorage(extension)) 83 return PRIV_ISOLATED; 84 if (extension && extension->is_hosted_app()) 85 return PRIV_HOSTED; 86 return PRIV_EXTENSION; 87} 88 89RenderProcessHostPrivilege GetProcessPrivilege( 90 content::RenderProcessHost* process_host, 91 ProcessMap* process_map, 92 ExtensionService* service) { 93 std::set<std::string> extension_ids = 94 process_map->GetExtensionsInProcess(process_host->GetID()); 95 if (extension_ids.empty()) 96 return PRIV_NORMAL; 97 98 for (std::set<std::string>::iterator iter = extension_ids.begin(); 99 iter != extension_ids.end(); ++iter) { 100 const Extension* extension = service->GetExtensionById(*iter, false); 101 if (extension && AppIsolationInfo::HasIsolatedStorage(extension)) 102 return PRIV_ISOLATED; 103 if (extension && extension->is_hosted_app()) 104 return PRIV_HOSTED; 105 } 106 107 return PRIV_EXTENSION; 108} 109 110} // namespace 111 112ChromeContentBrowserClientExtensionsPart:: 113 ChromeContentBrowserClientExtensionsPart() { 114 permissions_policy_delegate_.reset(new BrowserPermissionsPolicyDelegate()); 115} 116 117ChromeContentBrowserClientExtensionsPart:: 118 ~ChromeContentBrowserClientExtensionsPart() { 119} 120 121// static 122GURL ChromeContentBrowserClientExtensionsPart::GetEffectiveURL( 123 Profile* profile, const GURL& url) { 124 // If the input |url| is part of an installed app, the effective URL is an 125 // extension URL with the ID of that extension as the host. This has the 126 // effect of grouping apps together in a common SiteInstance. 127 ExtensionService* extension_service = 128 ExtensionSystem::Get(profile)->extension_service(); 129 if (!extension_service) 130 return url; 131 132 const Extension* extension = 133 extension_service->extensions()->GetHostedAppByURL(url); 134 if (!extension) 135 return url; 136 137 // Bookmark apps do not use the hosted app process model, and should be 138 // treated as normal URLs. 139 if (extension->from_bookmark()) 140 return url; 141 142 // If the URL is part of an extension's web extent, convert it to an 143 // extension URL. 144 return extension->GetResourceURL(url.path()); 145} 146 147// static 148bool ChromeContentBrowserClientExtensionsPart::ShouldUseProcessPerSite( 149 Profile* profile, const GURL& effective_url) { 150 if (!effective_url.SchemeIs(kExtensionScheme)) 151 return false; 152 153 ExtensionService* extension_service = 154 ExtensionSystem::Get(profile)->extension_service(); 155 if (!extension_service) 156 return false; 157 158 const Extension* extension = 159 extension_service->extensions()->GetExtensionOrAppByURL(effective_url); 160 if (!extension) 161 return false; 162 163 // If the URL is part of a hosted app that does not have the background 164 // permission, or that does not allow JavaScript access to the background 165 // page, we want to give each instance its own process to improve 166 // responsiveness. 167 if (extension->GetType() == Manifest::TYPE_HOSTED_APP) { 168 if (!extension->permissions_data()->HasAPIPermission( 169 APIPermission::kBackground) || 170 !BackgroundInfo::AllowJSAccess(extension)) { 171 return false; 172 } 173 } 174 175 // Hosted apps that have script access to their background page must use 176 // process per site, since all instances can make synchronous calls to the 177 // background window. Other extensions should use process per site as well. 178 return true; 179} 180 181// static 182bool ChromeContentBrowserClientExtensionsPart::CanCommitURL( 183 content::RenderProcessHost* process_host, const GURL& url) { 184 // We need to let most extension URLs commit in any process, since this can 185 // be allowed due to web_accessible_resources. Most hosted app URLs may also 186 // load in any process (e.g., in an iframe). However, the Chrome Web Store 187 // cannot be loaded in iframes and should never be requested outside its 188 // process. 189 Profile* profile = 190 Profile::FromBrowserContext(process_host->GetBrowserContext()); 191 ExtensionService* service = 192 ExtensionSystem::Get(profile)->extension_service(); 193 if (!service) 194 return true; 195 196 const Extension* new_extension = 197 service->extensions()->GetExtensionOrAppByURL(url); 198 if (new_extension && 199 new_extension->is_hosted_app() && 200 new_extension->id() == extensions::kWebStoreAppId && 201 !ProcessMap::Get(profile)->Contains( 202 new_extension->id(), process_host->GetID())) { 203 return false; 204 } 205 return true; 206} 207 208// static 209bool ChromeContentBrowserClientExtensionsPart::IsSuitableHost( 210 Profile* profile, 211 content::RenderProcessHost* process_host, 212 const GURL& site_url) { 213 DCHECK(profile); 214 215 ExtensionService* service = 216 ExtensionSystem::Get(profile)->extension_service(); 217 ProcessMap* process_map = ProcessMap::Get(profile); 218 219 // These may be NULL during tests. In that case, just assume any site can 220 // share any host. 221 if (!service || !process_map) 222 return true; 223 224 // Otherwise, just make sure the process privilege matches the privilege 225 // required by the site. 226 RenderProcessHostPrivilege privilege_required = 227 GetPrivilegeRequiredByUrl(site_url, service); 228 return GetProcessPrivilege(process_host, process_map, service) == 229 privilege_required; 230} 231 232// static 233bool 234ChromeContentBrowserClientExtensionsPart::ShouldTryToUseExistingProcessHost( 235 Profile* profile, const GURL& url) { 236 // This function is trying to limit the amount of processes used by extensions 237 // with background pages. It uses a globally set percentage of processes to 238 // run such extensions and if the limit is exceeded, it returns true, to 239 // indicate to the content module to group extensions together. 240 ExtensionService* service = profile ? 241 ExtensionSystem::Get(profile)->extension_service() : NULL; 242 if (!service) 243 return false; 244 245 // We have to have a valid extension with background page to proceed. 246 const Extension* extension = 247 service->extensions()->GetExtensionOrAppByURL(url); 248 if (!extension) 249 return false; 250 if (!BackgroundInfo::HasBackgroundPage(extension)) 251 return false; 252 253 std::set<int> process_ids; 254 size_t max_process_count = 255 content::RenderProcessHost::GetMaxRendererProcessCount(); 256 257 // Go through all profiles to ensure we have total count of extension 258 // processes containing background pages, otherwise one profile can 259 // starve the other. 260 std::vector<Profile*> profiles = g_browser_process->profile_manager()-> 261 GetLoadedProfiles(); 262 for (size_t i = 0; i < profiles.size(); ++i) { 263 ProcessManager* epm = ExtensionSystem::Get(profiles[i])->process_manager(); 264 for (ProcessManager::const_iterator iter = epm->background_hosts().begin(); 265 iter != epm->background_hosts().end(); ++iter) { 266 const ExtensionHost* host = *iter; 267 process_ids.insert(host->render_process_host()->GetID()); 268 } 269 } 270 271 return (process_ids.size() > 272 (max_process_count * chrome::kMaxShareOfExtensionProcesses)); 273} 274 275// static 276bool ChromeContentBrowserClientExtensionsPart:: 277 ShouldSwapBrowsingInstancesForNavigation(SiteInstance* site_instance, 278 const GURL& current_url, 279 const GURL& new_url) { 280 // If we don't have an ExtensionService, then rely on the SiteInstance logic 281 // in RenderFrameHostManager to decide when to swap. 282 Profile* profile = 283 Profile::FromBrowserContext(site_instance->GetBrowserContext()); 284 ExtensionService* service = 285 ExtensionSystem::Get(profile)->extension_service(); 286 if (!service) 287 return false; 288 289 // We must use a new BrowsingInstance (forcing a process swap and disabling 290 // scripting by existing tabs) if one of the URLs is an extension and the 291 // other is not the exact same extension. 292 // 293 // We ignore hosted apps here so that other tabs in their BrowsingInstance can 294 // use postMessage with them. (The exception is the Chrome Web Store, which 295 // is a hosted app that requires its own BrowsingInstance.) Navigations 296 // to/from a hosted app will still trigger a SiteInstance swap in 297 // RenderFrameHostManager. 298 const Extension* current_extension = 299 service->extensions()->GetExtensionOrAppByURL(current_url); 300 if (current_extension && 301 current_extension->is_hosted_app() && 302 current_extension->id() != extensions::kWebStoreAppId) 303 current_extension = NULL; 304 305 const Extension* new_extension = 306 service->extensions()->GetExtensionOrAppByURL(new_url); 307 if (new_extension && 308 new_extension->is_hosted_app() && 309 new_extension->id() != extensions::kWebStoreAppId) 310 new_extension = NULL; 311 312 // First do a process check. We should force a BrowsingInstance swap if the 313 // current process doesn't know about new_extension, even if current_extension 314 // is somehow the same as new_extension. 315 ProcessMap* process_map = ProcessMap::Get(profile); 316 if (new_extension && 317 site_instance->HasProcess() && 318 !process_map->Contains( 319 new_extension->id(), site_instance->GetProcess()->GetID())) 320 return true; 321 322 // Otherwise, swap BrowsingInstances if current_extension and new_extension 323 // differ. 324 return current_extension != new_extension; 325} 326 327// static 328bool ChromeContentBrowserClientExtensionsPart::ShouldSwapProcessesForRedirect( 329 content::ResourceContext* resource_context, 330 const GURL& current_url, 331 const GURL& new_url) { 332 ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context); 333 return CrossesExtensionProcessBoundary( 334 io_data->GetExtensionInfoMap()->extensions(), 335 current_url, new_url, false); 336} 337 338// static 339bool ChromeContentBrowserClientExtensionsPart::ShouldAllowOpenURL( 340 content::SiteInstance* site_instance, 341 const GURL& from_url, 342 const GURL& to_url, 343 bool* result) { 344 DCHECK(result); 345 346 // Do not allow pages from the web or other extensions navigate to 347 // non-web-accessible extension resources. 348 if (to_url.SchemeIs(kExtensionScheme) && 349 (from_url.SchemeIsHTTPOrHTTPS() || from_url.SchemeIs(kExtensionScheme))) { 350 Profile* profile = Profile::FromBrowserContext( 351 site_instance->GetProcess()->GetBrowserContext()); 352 ExtensionService* service = 353 ExtensionSystem::Get(profile)->extension_service(); 354 if (!service) { 355 *result = true; 356 return true; 357 } 358 const Extension* extension = 359 service->extensions()->GetExtensionOrAppByURL(to_url); 360 if (!extension) { 361 *result = true; 362 return true; 363 } 364 const Extension* from_extension = 365 service->extensions()->GetExtensionOrAppByURL( 366 site_instance->GetSiteURL()); 367 if (from_extension && from_extension->id() == extension->id()) { 368 *result = true; 369 return true; 370 } 371 372 if (!WebAccessibleResourcesInfo::IsResourceWebAccessible( 373 extension, to_url.path())) { 374 *result = false; 375 return true; 376 } 377 } 378 return false; 379} 380 381// static 382void ChromeContentBrowserClientExtensionsPart::SetSigninProcess( 383 content::SiteInstance* site_instance) { 384 Profile* profile = 385 Profile::FromBrowserContext(site_instance->GetBrowserContext()); 386 DCHECK(profile); 387 BrowserThread::PostTask( 388 BrowserThread::IO, 389 FROM_HERE, 390 base::Bind(&InfoMap::SetSigninProcess, 391 ExtensionSystem::Get(profile)->info_map(), 392 site_instance->GetProcess()->GetID())); 393} 394 395void ChromeContentBrowserClientExtensionsPart::RenderProcessWillLaunch( 396 content::RenderProcessHost* host) { 397 int id = host->GetID(); 398 Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext()); 399 400 host->AddFilter(new ChromeExtensionMessageFilter(id, profile)); 401 host->AddFilter(new ExtensionMessageFilter(id, profile)); 402 extension_web_request_api_helpers::SendExtensionWebRequestStatusToHost(host); 403} 404 405void ChromeContentBrowserClientExtensionsPart::SiteInstanceGotProcess( 406 SiteInstance* site_instance) { 407 Profile* profile = Profile::FromBrowserContext( 408 site_instance->GetProcess()->GetBrowserContext()); 409 ExtensionService* service = 410 ExtensionSystem::Get(profile)->extension_service(); 411 if (!service) 412 return; 413 414 const Extension* extension = service->extensions()->GetExtensionOrAppByURL( 415 site_instance->GetSiteURL()); 416 if (!extension) 417 return; 418 419 ProcessMap::Get(profile)->Insert(extension->id(), 420 site_instance->GetProcess()->GetID(), 421 site_instance->GetId()); 422 423 BrowserThread::PostTask(BrowserThread::IO, 424 FROM_HERE, 425 base::Bind(&InfoMap::RegisterExtensionProcess, 426 ExtensionSystem::Get(profile)->info_map(), 427 extension->id(), 428 site_instance->GetProcess()->GetID(), 429 site_instance->GetId())); 430} 431 432void ChromeContentBrowserClientExtensionsPart::SiteInstanceDeleting( 433 SiteInstance* site_instance) { 434 Profile* profile = 435 Profile::FromBrowserContext(site_instance->GetBrowserContext()); 436 ExtensionService* service = 437 ExtensionSystem::Get(profile)->extension_service(); 438 if (!service) 439 return; 440 441 const Extension* extension = service->extensions()->GetExtensionOrAppByURL( 442 site_instance->GetSiteURL()); 443 if (!extension) 444 return; 445 446 ProcessMap::Get(profile)->Remove(extension->id(), 447 site_instance->GetProcess()->GetID(), 448 site_instance->GetId()); 449 450 BrowserThread::PostTask(BrowserThread::IO, 451 FROM_HERE, 452 base::Bind(&InfoMap::UnregisterExtensionProcess, 453 ExtensionSystem::Get(profile)->info_map(), 454 extension->id(), 455 site_instance->GetProcess()->GetID(), 456 site_instance->GetId())); 457} 458 459void ChromeContentBrowserClientExtensionsPart::OverrideWebkitPrefs( 460 RenderViewHost* rvh, 461 const GURL& url, 462 WebPreferences* web_prefs) { 463 Profile* profile = 464 Profile::FromBrowserContext(rvh->GetProcess()->GetBrowserContext()); 465 466 ExtensionService* service = 467 ExtensionSystem::Get(profile)->extension_service(); 468 if (!service) 469 return; 470 471 // Note: it's not possible for kExtensionsScheme to change during the lifetime 472 // of the process. 473 // 474 // Ensure that we are only granting extension preferences to URLs with 475 // the correct scheme. Without this check, chrome-guest:// schemes used by 476 // webview tags as well as hosts that happen to match the id of an 477 // installed extension would get the wrong preferences. 478 const GURL& site_url = rvh->GetSiteInstance()->GetSiteURL(); 479 if (!site_url.SchemeIs(kExtensionScheme)) 480 return; 481 482 WebContents* web_contents = WebContents::FromRenderViewHost(rvh); 483 ViewType view_type = GetViewType(web_contents); 484 const Extension* extension = service->extensions()->GetByID(site_url.host()); 485 extension_webkit_preferences::SetPreferences(extension, view_type, web_prefs); 486} 487 488void ChromeContentBrowserClientExtensionsPart::BrowserURLHandlerCreated( 489 BrowserURLHandler* handler) { 490 handler->AddHandlerPair(&ExtensionWebUI::HandleChromeURLOverride, 491 BrowserURLHandler::null_handler()); 492 handler->AddHandlerPair(BrowserURLHandler::null_handler(), 493 &ExtensionWebUI::HandleChromeURLOverrideReverse); 494} 495 496void ChromeContentBrowserClientExtensionsPart:: 497 GetAdditionalAllowedSchemesForFileSystem( 498 std::vector<std::string>* additional_allowed_schemes) { 499 additional_allowed_schemes->push_back(kExtensionScheme); 500} 501 502void ChromeContentBrowserClientExtensionsPart::GetURLRequestAutoMountHandlers( 503 std::vector<storage::URLRequestAutoMountHandler>* handlers) { 504 handlers->push_back( 505 base::Bind(MediaFileSystemBackend::AttemptAutoMountForURLRequest)); 506} 507 508void ChromeContentBrowserClientExtensionsPart::GetAdditionalFileSystemBackends( 509 content::BrowserContext* browser_context, 510 const base::FilePath& storage_partition_path, 511 ScopedVector<storage::FileSystemBackend>* additional_backends) { 512 base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool(); 513 additional_backends->push_back(new MediaFileSystemBackend( 514 storage_partition_path, 515 pool->GetSequencedTaskRunner( 516 pool->GetNamedSequenceToken( 517 MediaFileSystemBackend::kMediaTaskRunnerName)).get())); 518 519 additional_backends->push_back(new sync_file_system::SyncFileSystemBackend( 520 Profile::FromBrowserContext(browser_context))); 521} 522 523void ChromeContentBrowserClientExtensionsPart:: 524 AppendExtraRendererCommandLineSwitches(base::CommandLine* command_line, 525 content::RenderProcessHost* process, 526 Profile* profile) { 527 if (!process) 528 return; 529 DCHECK(profile); 530 if (ProcessMap::Get(profile)->Contains(process->GetID())) { 531 command_line->AppendSwitch(switches::kExtensionProcess); 532#if defined(ENABLE_WEBRTC) 533 command_line->AppendSwitch(::switches::kEnableWebRtcHWH264Encoding); 534#endif 535 } 536} 537 538} // namespace extensions 539