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 "extensions/browser/api/runtime/runtime_api.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/metrics/histogram.h"
13 #include "base/values.h"
14 #include "base/version.h"
15 #include "content/public/browser/browser_context.h"
16 #include "content/public/browser/child_process_security_policy.h"
17 #include "content/public/browser/notification_service.h"
18 #include "content/public/browser/render_frame_host.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "extensions/browser/api/runtime/runtime_api_delegate.h"
21 #include "extensions/browser/event_router.h"
22 #include "extensions/browser/extension_host.h"
23 #include "extensions/browser/extension_prefs.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/browser/extension_system.h"
26 #include "extensions/browser/extension_util.h"
27 #include "extensions/browser/extensions_browser_client.h"
28 #include "extensions/browser/lazy_background_task_queue.h"
29 #include "extensions/browser/notification_types.h"
30 #include "extensions/browser/process_manager_factory.h"
31 #include "extensions/common/api/runtime.h"
32 #include "extensions/common/error_utils.h"
33 #include "extensions/common/extension.h"
34 #include "extensions/common/manifest_handlers/background_info.h"
35 #include "extensions/common/manifest_handlers/shared_module_info.h"
36 #include "storage/browser/fileapi/isolated_context.h"
39 using content::BrowserContext
;
41 namespace extensions
{
43 namespace runtime
= api::runtime
;
47 const char kNoBackgroundPageError
[] = "You do not have a background page.";
48 const char kPageLoadError
[] = "Background page failed to load.";
49 const char kFailedToCreateOptionsPage
[] = "Could not create an options page.";
50 const char kInstallId
[] = "id";
51 const char kInstallReason
[] = "reason";
52 const char kInstallReasonChromeUpdate
[] = "chrome_update";
53 const char kInstallReasonUpdate
[] = "update";
54 const char kInstallReasonInstall
[] = "install";
55 const char kInstallReasonSharedModuleUpdate
[] = "shared_module_update";
56 const char kInstallPreviousVersion
[] = "previousVersion";
57 const char kInvalidUrlError
[] = "Invalid URL: \"*\".";
58 const char kPlatformInfoUnavailable
[] = "Platform information unavailable.";
60 const char kUpdatesDisabledError
[] = "Autoupdate is not enabled.";
62 // A preference key storing the url loaded when an extension is uninstalled.
63 const char kUninstallUrl
[] = "uninstall_url";
65 // The name of the directory to be returned by getPackageDirectoryEntry. This
66 // particular value does not matter to user code, but is chosen for consistency
67 // with the equivalent Pepper API.
68 const char kPackageDirectoryPath
[] = "crxfs";
70 void DispatchOnStartupEventImpl(BrowserContext
* browser_context
,
71 const std::string
& extension_id
,
73 ExtensionHost
* host
) {
74 // A NULL host from the LazyBackgroundTaskQueue means the page failed to
76 if (!host
&& !first_call
)
79 // Don't send onStartup events to incognito browser contexts.
80 if (browser_context
->IsOffTheRecord())
83 if (ExtensionsBrowserClient::Get()->IsShuttingDown() ||
84 !ExtensionsBrowserClient::Get()->IsValidContext(browser_context
))
86 ExtensionSystem
* system
= ExtensionSystem::Get(browser_context
);
90 // If this is a persistent background page, we want to wait for it to load
91 // (it might not be ready, since this is startup). But only enqueue once.
92 // If it fails to load the first time, don't bother trying again.
93 const Extension
* extension
=
94 ExtensionRegistry::Get(browser_context
)->enabled_extensions().GetByID(
96 if (extension
&& BackgroundInfo::HasPersistentBackgroundPage(extension
) &&
98 LazyBackgroundTaskQueue::Get(browser_context
)
99 ->ShouldEnqueueTask(browser_context
, extension
)) {
100 LazyBackgroundTaskQueue::Get(browser_context
)
101 ->AddPendingTask(browser_context
, extension_id
,
102 base::Bind(&DispatchOnStartupEventImpl
,
103 browser_context
, extension_id
, false));
107 scoped_ptr
<base::ListValue
> event_args(new base::ListValue());
108 scoped_ptr
<Event
> event(new Event(events::RUNTIME_ON_STARTUP
,
109 runtime::OnStartup::kEventName
,
111 EventRouter::Get(browser_context
)
112 ->DispatchEventToExtension(extension_id
, event
.Pass());
115 void SetUninstallURL(ExtensionPrefs
* prefs
,
116 const std::string
& extension_id
,
117 const std::string
& url_string
) {
118 prefs
->UpdateExtensionPref(
119 extension_id
, kUninstallUrl
, new base::StringValue(url_string
));
122 std::string
GetUninstallURL(ExtensionPrefs
* prefs
,
123 const std::string
& extension_id
) {
124 std::string url_string
;
125 prefs
->ReadPrefAsString(extension_id
, kUninstallUrl
, &url_string
);
131 ///////////////////////////////////////////////////////////////////////////////
133 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<RuntimeAPI
> >
134 g_factory
= LAZY_INSTANCE_INITIALIZER
;
137 BrowserContextKeyedAPIFactory
<RuntimeAPI
>* RuntimeAPI::GetFactoryInstance() {
138 return g_factory
.Pointer();
142 void BrowserContextKeyedAPIFactory
<RuntimeAPI
>::DeclareFactoryDependencies() {
143 DependsOn(ProcessManagerFactory::GetInstance());
146 RuntimeAPI::RuntimeAPI(content::BrowserContext
* context
)
147 : browser_context_(context
),
148 dispatch_chrome_updated_event_(false),
149 extension_registry_observer_(this),
150 process_manager_observer_(this) {
151 // RuntimeAPI is redirected in incognito, so |browser_context_| is never
153 DCHECK(!browser_context_
->IsOffTheRecord());
156 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED
,
157 content::Source
<BrowserContext
>(context
));
158 extension_registry_observer_
.Add(ExtensionRegistry::Get(browser_context_
));
159 process_manager_observer_
.Add(ProcessManager::Get(browser_context_
));
161 delegate_
= ExtensionsBrowserClient::Get()->CreateRuntimeAPIDelegate(
164 // Check if registered events are up-to-date. We can only do this once
165 // per browser context, since it updates internal state when called.
166 dispatch_chrome_updated_event_
=
167 ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_
);
170 RuntimeAPI::~RuntimeAPI() {
173 void RuntimeAPI::Observe(int type
,
174 const content::NotificationSource
& source
,
175 const content::NotificationDetails
& details
) {
176 DCHECK_EQ(extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED
, type
);
177 // We're done restarting Chrome after an update.
178 dispatch_chrome_updated_event_
= false;
180 delegate_
->AddUpdateObserver(this);
183 void RuntimeAPI::OnExtensionLoaded(content::BrowserContext
* browser_context
,
184 const Extension
* extension
) {
185 if (!dispatch_chrome_updated_event_
)
188 // Dispatch the onInstalled event with reason "chrome_update".
189 base::MessageLoop::current()->PostTask(
191 base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent
,
198 void RuntimeAPI::OnExtensionWillBeInstalled(
199 content::BrowserContext
* browser_context
,
200 const Extension
* extension
,
203 const std::string
& old_name
) {
204 // Ephemeral apps are not considered to be installed and do not receive
205 // the onInstalled() event.
206 if (util::IsEphemeralApp(extension
->id(), browser_context_
))
209 Version old_version
= delegate_
->GetPreviousExtensionVersion(extension
);
211 // Dispatch the onInstalled event.
212 base::MessageLoop::current()->PostTask(
214 base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent
,
221 void RuntimeAPI::OnExtensionUninstalled(
222 content::BrowserContext
* browser_context
,
223 const Extension
* extension
,
224 UninstallReason reason
) {
225 // Ephemeral apps are not considered to be installed, so the uninstall URL
226 // is not invoked when they are removed.
227 if (util::IsEphemeralApp(extension
->id(), browser_context_
))
230 RuntimeEventRouter::OnExtensionUninstalled(
231 browser_context_
, extension
->id(), reason
);
234 void RuntimeAPI::Shutdown() {
235 delegate_
->RemoveUpdateObserver(this);
238 void RuntimeAPI::OnAppUpdateAvailable(const Extension
* extension
) {
239 RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
240 browser_context_
, extension
->id(), extension
->manifest()->value());
243 void RuntimeAPI::OnChromeUpdateAvailable() {
244 RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(browser_context_
);
247 void RuntimeAPI::OnBackgroundHostStartup(const Extension
* extension
) {
248 RuntimeEventRouter::DispatchOnStartupEvent(browser_context_
, extension
->id());
251 void RuntimeAPI::ReloadExtension(const std::string
& extension_id
) {
252 delegate_
->ReloadExtension(extension_id
);
255 bool RuntimeAPI::CheckForUpdates(
256 const std::string
& extension_id
,
257 const RuntimeAPIDelegate::UpdateCheckCallback
& callback
) {
258 return delegate_
->CheckForUpdates(extension_id
, callback
);
261 void RuntimeAPI::OpenURL(const GURL
& update_url
) {
262 delegate_
->OpenURL(update_url
);
265 bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo
* info
) {
266 return delegate_
->GetPlatformInfo(info
);
269 bool RuntimeAPI::RestartDevice(std::string
* error_message
) {
270 return delegate_
->RestartDevice(error_message
);
273 bool RuntimeAPI::OpenOptionsPage(const Extension
* extension
) {
274 return delegate_
->OpenOptionsPage(extension
);
277 ///////////////////////////////////////////////////////////////////////////////
280 void RuntimeEventRouter::DispatchOnStartupEvent(
281 content::BrowserContext
* context
,
282 const std::string
& extension_id
) {
283 DispatchOnStartupEventImpl(context
, extension_id
, true, NULL
);
287 void RuntimeEventRouter::DispatchOnInstalledEvent(
288 content::BrowserContext
* context
,
289 const std::string
& extension_id
,
290 const Version
& old_version
,
291 bool chrome_updated
) {
292 if (!ExtensionsBrowserClient::Get()->IsValidContext(context
))
294 ExtensionSystem
* system
= ExtensionSystem::Get(context
);
298 scoped_ptr
<base::ListValue
> event_args(new base::ListValue());
299 base::DictionaryValue
* info
= new base::DictionaryValue();
300 event_args
->Append(info
);
301 if (old_version
.IsValid()) {
302 info
->SetString(kInstallReason
, kInstallReasonUpdate
);
303 info
->SetString(kInstallPreviousVersion
, old_version
.GetString());
304 } else if (chrome_updated
) {
305 info
->SetString(kInstallReason
, kInstallReasonChromeUpdate
);
307 info
->SetString(kInstallReason
, kInstallReasonInstall
);
309 EventRouter
* event_router
= EventRouter::Get(context
);
310 DCHECK(event_router
);
311 scoped_ptr
<Event
> event(new Event(events::RUNTIME_ON_INSTALLED
,
312 runtime::OnInstalled::kEventName
,
314 event_router
->DispatchEventWithLazyListener(extension_id
, event
.Pass());
316 if (old_version
.IsValid()) {
317 const Extension
* extension
=
318 ExtensionRegistry::Get(context
)->enabled_extensions().GetByID(
320 if (extension
&& SharedModuleInfo::IsSharedModule(extension
)) {
321 scoped_ptr
<ExtensionSet
> dependents
=
322 system
->GetDependentExtensions(extension
);
323 for (ExtensionSet::const_iterator i
= dependents
->begin();
324 i
!= dependents
->end();
326 scoped_ptr
<base::ListValue
> sm_event_args(new base::ListValue());
327 base::DictionaryValue
* sm_info
= new base::DictionaryValue();
328 sm_event_args
->Append(sm_info
);
329 sm_info
->SetString(kInstallReason
, kInstallReasonSharedModuleUpdate
);
330 sm_info
->SetString(kInstallPreviousVersion
, old_version
.GetString());
331 sm_info
->SetString(kInstallId
, extension_id
);
332 scoped_ptr
<Event
> sm_event(new Event(events::RUNTIME_ON_INSTALLED
,
333 runtime::OnInstalled::kEventName
,
334 sm_event_args
.Pass()));
335 event_router
->DispatchEventWithLazyListener((*i
)->id(),
343 void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
344 content::BrowserContext
* context
,
345 const std::string
& extension_id
,
346 const base::DictionaryValue
* manifest
) {
347 ExtensionSystem
* system
= ExtensionSystem::Get(context
);
351 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
352 args
->Append(manifest
->DeepCopy());
353 EventRouter
* event_router
= EventRouter::Get(context
);
354 DCHECK(event_router
);
355 scoped_ptr
<Event
> event(new Event(events::RUNTIME_ON_UPDATE_AVAILABLE
,
356 runtime::OnUpdateAvailable::kEventName
,
358 event_router
->DispatchEventToExtension(extension_id
, event
.Pass());
362 void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(
363 content::BrowserContext
* context
) {
364 ExtensionSystem
* system
= ExtensionSystem::Get(context
);
368 scoped_ptr
<base::ListValue
> args(new base::ListValue
);
369 EventRouter
* event_router
= EventRouter::Get(context
);
370 DCHECK(event_router
);
371 scoped_ptr
<Event
> event(
372 new Event(events::RUNTIME_ON_BROWSER_UPDATE_AVAILABLE
,
373 runtime::OnBrowserUpdateAvailable::kEventName
, args
.Pass()));
374 event_router
->BroadcastEvent(event
.Pass());
378 void RuntimeEventRouter::DispatchOnRestartRequiredEvent(
379 content::BrowserContext
* context
,
380 const std::string
& app_id
,
381 api::runtime::OnRestartRequiredReason reason
) {
382 ExtensionSystem
* system
= ExtensionSystem::Get(context
);
386 scoped_ptr
<Event
> event(
387 new Event(events::RUNTIME_ON_RESTART_REQUIRED
,
388 runtime::OnRestartRequired::kEventName
,
389 api::runtime::OnRestartRequired::Create(reason
)));
390 EventRouter
* event_router
= EventRouter::Get(context
);
391 DCHECK(event_router
);
392 event_router
->DispatchEventToExtension(app_id
, event
.Pass());
396 void RuntimeEventRouter::OnExtensionUninstalled(
397 content::BrowserContext
* context
,
398 const std::string
& extension_id
,
399 UninstallReason reason
) {
400 if (!(reason
== UNINSTALL_REASON_USER_INITIATED
||
401 reason
== UNINSTALL_REASON_MANAGEMENT_API
)) {
406 GetUninstallURL(ExtensionPrefs::Get(context
), extension_id
));
408 if (!uninstall_url
.SchemeIsHTTPOrHTTPS()) {
409 // Previous versions of Chrome allowed non-http(s) URLs to be stored in the
410 // prefs. Now they're disallowed, but the old data may still exist.
414 RuntimeAPI::GetFactoryInstance()->Get(context
)->OpenURL(uninstall_url
);
417 ExtensionFunction::ResponseAction
RuntimeGetBackgroundPageFunction::Run() {
418 ExtensionHost
* host
= ProcessManager::Get(browser_context())
419 ->GetBackgroundHostForExtension(extension_id());
420 if (LazyBackgroundTaskQueue::Get(browser_context())
421 ->ShouldEnqueueTask(browser_context(), extension())) {
422 LazyBackgroundTaskQueue::Get(browser_context())
424 browser_context(), extension_id(),
425 base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded
, this));
429 return RespondNow(Error(kNoBackgroundPageError
));
432 return RespondLater();
435 void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost
* host
) {
437 Respond(NoArguments());
439 Respond(Error(kPageLoadError
));
443 ExtensionFunction::ResponseAction
RuntimeOpenOptionsPageFunction::Run() {
444 RuntimeAPI
* api
= RuntimeAPI::GetFactoryInstance()->Get(browser_context());
445 return RespondNow(api
->OpenOptionsPage(extension())
447 : Error(kFailedToCreateOptionsPage
));
450 ExtensionFunction::ResponseAction
RuntimeSetUninstallURLFunction::Run() {
451 std::string url_string
;
452 EXTENSION_FUNCTION_VALIDATE(args_
->GetString(0, &url_string
));
454 if (!url_string
.empty() && !GURL(url_string
).SchemeIsHTTPOrHTTPS()) {
455 return RespondNow(Error(kInvalidUrlError
, url_string
));
458 ExtensionPrefs::Get(browser_context()), extension_id(), url_string
);
459 return RespondNow(NoArguments());
462 ExtensionFunction::ResponseAction
RuntimeReloadFunction::Run() {
463 RuntimeAPI::GetFactoryInstance()->Get(browser_context())->ReloadExtension(
465 return RespondNow(NoArguments());
468 ExtensionFunction::ResponseAction
RuntimeRequestUpdateCheckFunction::Run() {
469 if (!RuntimeAPI::GetFactoryInstance()
470 ->Get(browser_context())
473 base::Bind(&RuntimeRequestUpdateCheckFunction::CheckComplete
,
475 return RespondNow(Error(kUpdatesDisabledError
));
477 return RespondLater();
480 void RuntimeRequestUpdateCheckFunction::CheckComplete(
481 const RuntimeAPIDelegate::UpdateCheckResult
& result
) {
482 if (result
.success
) {
483 base::DictionaryValue
* details
= new base::DictionaryValue
;
484 details
->SetString("version", result
.version
);
485 Respond(TwoArguments(new base::StringValue(result
.response
), details
));
487 // HMM(kalman): Why does !success not imply Error()?
488 Respond(OneArgument(new base::StringValue(result
.response
)));
492 ExtensionFunction::ResponseAction
RuntimeRestartFunction::Run() {
495 RuntimeAPI::GetFactoryInstance()->Get(browser_context())->RestartDevice(
498 return RespondNow(Error(message
));
500 return RespondNow(NoArguments());
503 ExtensionFunction::ResponseAction
RuntimeGetPlatformInfoFunction::Run() {
504 runtime::PlatformInfo info
;
505 if (!RuntimeAPI::GetFactoryInstance()
506 ->Get(browser_context())
507 ->GetPlatformInfo(&info
)) {
508 return RespondNow(Error(kPlatformInfoUnavailable
));
511 ArgumentList(runtime::GetPlatformInfo::Results::Create(info
)));
514 ExtensionFunction::ResponseAction
515 RuntimeGetPackageDirectoryEntryFunction::Run() {
516 storage::IsolatedContext
* isolated_context
=
517 storage::IsolatedContext::GetInstance();
518 DCHECK(isolated_context
);
520 std::string relative_path
= kPackageDirectoryPath
;
521 base::FilePath path
= extension_
->path();
522 std::string filesystem_id
= isolated_context
->RegisterFileSystemForPath(
523 storage::kFileSystemTypeNativeLocal
, std::string(), path
, &relative_path
);
525 int renderer_id
= render_frame_host()->GetProcess()->GetID();
526 content::ChildProcessSecurityPolicy
* policy
=
527 content::ChildProcessSecurityPolicy::GetInstance();
528 policy
->GrantReadFileSystem(renderer_id
, filesystem_id
);
529 base::DictionaryValue
* dict
= new base::DictionaryValue();
530 dict
->SetString("fileSystemId", filesystem_id
);
531 dict
->SetString("baseName", relative_path
);
532 return RespondNow(OneArgument(dict
));
535 } // namespace extensions