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