Home | History | Annotate | Download | only in extensions
      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 
     44 using content::BrowserThread;
     45 using content::BrowserURLHandler;
     46 using content::RenderViewHost;
     47 using content::SiteInstance;
     48 using content::WebContents;
     49 using content::WebPreferences;
     50 
     51 namespace extensions {
     52 
     53 namespace {
     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.
     59 enum RenderProcessHostPrivilege {
     60   PRIV_NORMAL,
     61   PRIV_HOSTED,
     62   PRIV_ISOLATED,
     63   PRIV_EXTENSION,
     64 };
     65 
     66 RenderProcessHostPrivilege 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 
     89 RenderProcessHostPrivilege 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 
    112 ChromeContentBrowserClientExtensionsPart::
    113     ChromeContentBrowserClientExtensionsPart() {
    114   permissions_policy_delegate_.reset(new BrowserPermissionsPolicyDelegate());
    115 }
    116 
    117 ChromeContentBrowserClientExtensionsPart::
    118     ~ChromeContentBrowserClientExtensionsPart() {
    119 }
    120 
    121 // static
    122 GURL 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
    148 bool 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
    182 bool 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
    209 bool 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
    233 bool
    234 ChromeContentBrowserClientExtensionsPart::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
    276 bool 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
    328 bool 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
    339 bool 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
    382 void 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 
    395 void 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 
    405 void 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 
    432 void 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 
    459 void 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 
    488 void 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 
    496 void ChromeContentBrowserClientExtensionsPart::
    497     GetAdditionalAllowedSchemesForFileSystem(
    498         std::vector<std::string>* additional_allowed_schemes) {
    499   additional_allowed_schemes->push_back(kExtensionScheme);
    500 }
    501 
    502 void ChromeContentBrowserClientExtensionsPart::GetURLRequestAutoMountHandlers(
    503     std::vector<storage::URLRequestAutoMountHandler>* handlers) {
    504   handlers->push_back(
    505       base::Bind(MediaFileSystemBackend::AttemptAutoMountForURLRequest));
    506 }
    507 
    508 void 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 
    523 void 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