MacViews: Get c/b/ui/views/tabs to build on Mac
[chromium-blink-merge.git] / chrome / browser / extensions / chrome_content_browser_client_extensions_part.cc
blob84b25f281ce9e862f3af56624a797cf4e7a53598
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.
5 #include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
7 #include <set>
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"
44 using content::BrowserThread;
45 using content::BrowserURLHandler;
46 using content::RenderViewHost;
47 using content::SiteInstance;
48 using content::WebContents;
49 using content::WebPreferences;
51 namespace extensions {
53 namespace {
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,
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;
78 if (!url.SchemeIs(kExtensionScheme))
79 return PRIV_NORMAL;
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;
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;
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;
107 return PRIV_EXTENSION;
110 } // namespace
112 ChromeContentBrowserClientExtensionsPart::
113 ChromeContentBrowserClientExtensionsPart() {
114 permissions_policy_delegate_.reset(new BrowserPermissionsPolicyDelegate());
117 ChromeContentBrowserClientExtensionsPart::
118 ~ChromeContentBrowserClientExtensionsPart() {
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;
132 const Extension* extension =
133 extension_service->extensions()->GetHostedAppByURL(url);
134 if (!extension)
135 return url;
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;
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());
147 // static
148 bool ChromeContentBrowserClientExtensionsPart::ShouldUseProcessPerSite(
149 Profile* profile, const GURL& effective_url) {
150 if (!effective_url.SchemeIs(kExtensionScheme))
151 return false;
153 ExtensionService* extension_service =
154 ExtensionSystem::Get(profile)->extension_service();
155 if (!extension_service)
156 return false;
158 const Extension* extension =
159 extension_service->extensions()->GetExtensionOrAppByURL(effective_url);
160 if (!extension)
161 return false;
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;
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;
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;
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;
205 return true;
208 // static
209 bool ChromeContentBrowserClientExtensionsPart::IsSuitableHost(
210 Profile* profile,
211 content::RenderProcessHost* process_host,
212 const GURL& site_url) {
213 DCHECK(profile);
215 ExtensionService* service =
216 ExtensionSystem::Get(profile)->extension_service();
217 ProcessMap* process_map = ProcessMap::Get(profile);
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;
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;
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;
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;
253 std::set<int> process_ids;
254 size_t max_process_count =
255 content::RenderProcessHost::GetMaxRendererProcessCount();
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());
271 return (process_ids.size() >
272 (max_process_count * chrome::kMaxShareOfExtensionProcesses));
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;
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.
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;
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;
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;
322 // Otherwise, swap BrowsingInstances if current_extension and new_extension
323 // differ.
324 return current_extension != new_extension;
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);
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);
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;
358 const Extension* extension =
359 service->extensions()->GetExtensionOrAppByURL(to_url);
360 if (!extension) {
361 *result = true;
362 return true;
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;
372 if (!WebAccessibleResourcesInfo::IsResourceWebAccessible(
373 extension, to_url.path())) {
374 *result = false;
375 return true;
378 return false;
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()));
395 void ChromeContentBrowserClientExtensionsPart::RenderProcessWillLaunch(
396 content::RenderProcessHost* host) {
397 int id = host->GetID();
398 Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
400 host->AddFilter(new ChromeExtensionMessageFilter(id, profile));
401 host->AddFilter(new ExtensionMessageFilter(id, profile));
402 extension_web_request_api_helpers::SendExtensionWebRequestStatusToHost(host);
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;
414 const Extension* extension = service->extensions()->GetExtensionOrAppByURL(
415 site_instance->GetSiteURL());
416 if (!extension)
417 return;
419 ProcessMap::Get(profile)->Insert(extension->id(),
420 site_instance->GetProcess()->GetID(),
421 site_instance->GetId());
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()));
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;
441 const Extension* extension = service->extensions()->GetExtensionOrAppByURL(
442 site_instance->GetSiteURL());
443 if (!extension)
444 return;
446 ProcessMap::Get(profile)->Remove(extension->id(),
447 site_instance->GetProcess()->GetID(),
448 site_instance->GetId());
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()));
459 void ChromeContentBrowserClientExtensionsPart::OverrideWebkitPrefs(
460 RenderViewHost* rvh,
461 const GURL& url,
462 WebPreferences* web_prefs) {
463 Profile* profile =
464 Profile::FromBrowserContext(rvh->GetProcess()->GetBrowserContext());
466 ExtensionService* service =
467 ExtensionSystem::Get(profile)->extension_service();
468 if (!service)
469 return;
471 // Note: it's not possible for kExtensionsScheme to change during the lifetime
472 // of the process.
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;
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);
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);
496 void ChromeContentBrowserClientExtensionsPart::
497 GetAdditionalAllowedSchemesForFileSystem(
498 std::vector<std::string>* additional_allowed_schemes) {
499 additional_allowed_schemes->push_back(kExtensionScheme);
502 void ChromeContentBrowserClientExtensionsPart::GetURLRequestAutoMountHandlers(
503 std::vector<storage::URLRequestAutoMountHandler>* handlers) {
504 handlers->push_back(
505 base::Bind(MediaFileSystemBackend::AttemptAutoMountForURLRequest));
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()));
519 additional_backends->push_back(new sync_file_system::SyncFileSystemBackend(
520 Profile::FromBrowserContext(browser_context)));
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
538 } // namespace extensions