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 "extensions/browser/api/runtime/runtime_api.h"
6
7#include <utility>
8
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 "chrome/browser/chrome_notification_types.h"
16#include "content/public/browser/browser_context.h"
17#include "content/public/browser/child_process_security_policy.h"
18#include "content/public/browser/notification_service.h"
19#include "content/public/browser/render_process_host.h"
20#include "content/public/browser/render_view_host.h"
21#include "extensions/browser/api/runtime/runtime_api_delegate.h"
22#include "extensions/browser/event_router.h"
23#include "extensions/browser/extension_host.h"
24#include "extensions/browser/extension_prefs.h"
25#include "extensions/browser/extension_registry.h"
26#include "extensions/browser/extension_system.h"
27#include "extensions/browser/extension_util.h"
28#include "extensions/browser/extensions_browser_client.h"
29#include "extensions/browser/lazy_background_task_queue.h"
30#include "extensions/browser/process_manager.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 "url/gurl.h"
37#include "webkit/browser/fileapi/isolated_context.h"
38
39using content::BrowserContext;
40
41namespace extensions {
42
43namespace runtime = core_api::runtime;
44
45namespace {
46
47const char kNoBackgroundPageError[] = "You do not have a background page.";
48const char kPageLoadError[] = "Background page failed to load.";
49const char kInstallId[] = "id";
50const char kInstallReason[] = "reason";
51const char kInstallReasonChromeUpdate[] = "chrome_update";
52const char kInstallReasonUpdate[] = "update";
53const char kInstallReasonInstall[] = "install";
54const char kInstallReasonSharedModuleUpdate[] = "shared_module_update";
55const char kInstallPreviousVersion[] = "previousVersion";
56const char kInvalidUrlError[] = "Invalid URL.";
57const char kPlatformInfoUnavailable[] = "Platform information unavailable.";
58
59const char kUpdatesDisabledError[] = "Autoupdate is not enabled.";
60
61// A preference key storing the url loaded when an extension is uninstalled.
62const char kUninstallUrl[] = "uninstall_url";
63
64// The name of the directory to be returned by getPackageDirectoryEntry. This
65// particular value does not matter to user code, but is chosen for consistency
66// with the equivalent Pepper API.
67const char kPackageDirectoryPath[] = "crxfs";
68
69void DispatchOnStartupEventImpl(BrowserContext* browser_context,
70                                const std::string& extension_id,
71                                bool first_call,
72                                ExtensionHost* host) {
73  // A NULL host from the LazyBackgroundTaskQueue means the page failed to
74  // load. Give up.
75  if (!host && !first_call)
76    return;
77
78  // Don't send onStartup events to incognito browser contexts.
79  if (browser_context->IsOffTheRecord())
80    return;
81
82  if (ExtensionsBrowserClient::Get()->IsShuttingDown() ||
83      !ExtensionsBrowserClient::Get()->IsValidContext(browser_context))
84    return;
85  ExtensionSystem* system = ExtensionSystem::Get(browser_context);
86  if (!system)
87    return;
88
89  // If this is a persistent background page, we want to wait for it to load
90  // (it might not be ready, since this is startup). But only enqueue once.
91  // If it fails to load the first time, don't bother trying again.
92  const Extension* extension =
93      ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID(
94          extension_id);
95  if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) &&
96      first_call &&
97      system->lazy_background_task_queue()->ShouldEnqueueTask(browser_context,
98                                                              extension)) {
99    system->lazy_background_task_queue()->AddPendingTask(
100        browser_context,
101        extension_id,
102        base::Bind(
103            &DispatchOnStartupEventImpl, browser_context, extension_id, false));
104    return;
105  }
106
107  scoped_ptr<base::ListValue> event_args(new base::ListValue());
108  scoped_ptr<Event> event(
109      new Event(runtime::OnStartup::kEventName, event_args.Pass()));
110  system->event_router()->DispatchEventToExtension(extension_id, event.Pass());
111}
112
113void SetUninstallURL(ExtensionPrefs* prefs,
114                     const std::string& extension_id,
115                     const std::string& url_string) {
116  prefs->UpdateExtensionPref(
117      extension_id, kUninstallUrl, new base::StringValue(url_string));
118}
119
120#if defined(ENABLE_EXTENSIONS)
121std::string GetUninstallURL(ExtensionPrefs* prefs,
122                            const std::string& extension_id) {
123  std::string url_string;
124  prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string);
125  return url_string;
126}
127#endif  // defined(ENABLE_EXTENSIONS)
128
129}  // namespace
130
131///////////////////////////////////////////////////////////////////////////////
132
133static base::LazyInstance<BrowserContextKeyedAPIFactory<RuntimeAPI> >
134    g_factory = LAZY_INSTANCE_INITIALIZER;
135
136// static
137BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() {
138  return g_factory.Pointer();
139}
140
141RuntimeAPI::RuntimeAPI(content::BrowserContext* context)
142    : browser_context_(context), dispatch_chrome_updated_event_(false) {
143  registrar_.Add(this,
144                 chrome::NOTIFICATION_EXTENSIONS_READY,
145                 content::Source<BrowserContext>(context));
146  registrar_.Add(this,
147                 chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
148                 content::Source<BrowserContext>(context));
149  registrar_.Add(this,
150                 chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED,
151                 content::Source<BrowserContext>(context));
152  registrar_.Add(this,
153                 chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED,
154                 content::Source<BrowserContext>(context));
155
156  delegate_ = ExtensionsBrowserClient::Get()->CreateRuntimeAPIDelegate(
157      browser_context_);
158
159  // Check if registered events are up-to-date. We can only do this once
160  // per browser context, since it updates internal state when called.
161  dispatch_chrome_updated_event_ =
162      ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_);
163}
164
165RuntimeAPI::~RuntimeAPI() {
166  delegate_->RemoveUpdateObserver(this);
167}
168
169void RuntimeAPI::Observe(int type,
170                         const content::NotificationSource& source,
171                         const content::NotificationDetails& details) {
172  switch (type) {
173    case chrome::NOTIFICATION_EXTENSIONS_READY: {
174      OnExtensionsReady();
175      break;
176    }
177    case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
178      const Extension* extension =
179          content::Details<const Extension>(details).ptr();
180      OnExtensionLoaded(extension);
181      break;
182    }
183    case chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED: {
184      const Extension* extension =
185          content::Details<const InstalledExtensionInfo>(details)->extension;
186      OnExtensionInstalled(extension);
187      break;
188    }
189    case chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED: {
190      const Extension* extension =
191          content::Details<const Extension>(details).ptr();
192      OnExtensionUninstalled(extension);
193      break;
194    }
195    default:
196      NOTREACHED();
197      break;
198  }
199}
200
201void RuntimeAPI::OnExtensionsReady() {
202  // We're done restarting Chrome after an update.
203  dispatch_chrome_updated_event_ = false;
204
205  delegate_->AddUpdateObserver(this);
206
207  // RuntimeAPI is redirected in incognito, so |browser_context_| is never
208  // incognito. We don't observe incognito ProcessManagers but that is OK
209  // because we don't send onStartup events to incognito browser contexts.
210  DCHECK(!browser_context_->IsOffTheRecord());
211  // Some tests use partially constructed Profiles without a process manager.
212  ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_);
213  if (extension_system->process_manager())
214    extension_system->process_manager()->AddObserver(this);
215}
216
217void RuntimeAPI::OnExtensionLoaded(const Extension* extension) {
218  if (!dispatch_chrome_updated_event_)
219    return;
220
221  // Dispatch the onInstalled event with reason "chrome_update".
222  base::MessageLoop::current()->PostTask(
223      FROM_HERE,
224      base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
225                 browser_context_,
226                 extension->id(),
227                 Version(),
228                 true));
229}
230
231void RuntimeAPI::OnExtensionInstalled(const Extension* extension) {
232  // Ephemeral apps are not considered to be installed and do not receive
233  // the onInstalled() event.
234  if (util::IsEphemeralApp(extension->id(), browser_context_))
235    return;
236
237  Version old_version = delegate_->GetPreviousExtensionVersion(extension);
238
239  // Dispatch the onInstalled event.
240  base::MessageLoop::current()->PostTask(
241      FROM_HERE,
242      base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
243                 browser_context_,
244                 extension->id(),
245                 old_version,
246                 false));
247}
248
249void RuntimeAPI::OnExtensionUninstalled(const Extension* extension) {
250  // Ephemeral apps are not considered to be installed, so the uninstall URL
251  // is not invoked when they are removed.
252  if (util::IsEphemeralApp(extension->id(), browser_context_))
253    return;
254
255  RuntimeEventRouter::OnExtensionUninstalled(browser_context_, extension->id());
256}
257
258void RuntimeAPI::Shutdown() {
259  // ExtensionSystem deletes its ProcessManager during the Shutdown() phase, so
260  // the observer must be removed here and not in the RuntimeAPI destructor.
261  ProcessManager* process_manager =
262      ExtensionSystem::Get(browser_context_)->process_manager();
263  // Some tests use partially constructed Profiles without a process manager.
264  if (process_manager)
265    process_manager->RemoveObserver(this);
266}
267
268void RuntimeAPI::OnAppUpdateAvailable(const Extension* extension) {
269  RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
270      browser_context_, extension->id(), extension->manifest()->value());
271}
272
273void RuntimeAPI::OnChromeUpdateAvailable() {
274  RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(browser_context_);
275}
276
277void RuntimeAPI::OnBackgroundHostStartup(const Extension* extension) {
278  RuntimeEventRouter::DispatchOnStartupEvent(browser_context_, extension->id());
279}
280
281void RuntimeAPI::ReloadExtension(const std::string& extension_id) {
282  delegate_->ReloadExtension(extension_id);
283}
284
285bool RuntimeAPI::CheckForUpdates(
286    const std::string& extension_id,
287    const RuntimeAPIDelegate::UpdateCheckCallback& callback) {
288  return delegate_->CheckForUpdates(extension_id, callback);
289}
290
291void RuntimeAPI::OpenURL(const GURL& update_url) {
292  delegate_->OpenURL(update_url);
293}
294
295bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo* info) {
296  return delegate_->GetPlatformInfo(info);
297}
298
299bool RuntimeAPI::RestartDevice(std::string* error_message) {
300  return delegate_->RestartDevice(error_message);
301}
302
303///////////////////////////////////////////////////////////////////////////////
304
305// static
306void RuntimeEventRouter::DispatchOnStartupEvent(
307    content::BrowserContext* context,
308    const std::string& extension_id) {
309  DispatchOnStartupEventImpl(context, extension_id, true, NULL);
310}
311
312// static
313void RuntimeEventRouter::DispatchOnInstalledEvent(
314    content::BrowserContext* context,
315    const std::string& extension_id,
316    const Version& old_version,
317    bool chrome_updated) {
318  if (!ExtensionsBrowserClient::Get()->IsValidContext(context))
319    return;
320  ExtensionSystem* system = ExtensionSystem::Get(context);
321  if (!system)
322    return;
323
324  scoped_ptr<base::ListValue> event_args(new base::ListValue());
325  base::DictionaryValue* info = new base::DictionaryValue();
326  event_args->Append(info);
327  if (old_version.IsValid()) {
328    info->SetString(kInstallReason, kInstallReasonUpdate);
329    info->SetString(kInstallPreviousVersion, old_version.GetString());
330  } else if (chrome_updated) {
331    info->SetString(kInstallReason, kInstallReasonChromeUpdate);
332  } else {
333    info->SetString(kInstallReason, kInstallReasonInstall);
334  }
335  DCHECK(system->event_router());
336  scoped_ptr<Event> event(
337      new Event(runtime::OnInstalled::kEventName, event_args.Pass()));
338  system->event_router()->DispatchEventWithLazyListener(extension_id,
339                                                        event.Pass());
340
341  if (old_version.IsValid()) {
342    const Extension* extension =
343        ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
344            extension_id);
345    if (extension && SharedModuleInfo::IsSharedModule(extension)) {
346      scoped_ptr<ExtensionSet> dependents =
347          system->GetDependentExtensions(extension);
348      for (ExtensionSet::const_iterator i = dependents->begin();
349           i != dependents->end();
350           i++) {
351        scoped_ptr<base::ListValue> sm_event_args(new base::ListValue());
352        base::DictionaryValue* sm_info = new base::DictionaryValue();
353        sm_event_args->Append(sm_info);
354        sm_info->SetString(kInstallReason, kInstallReasonSharedModuleUpdate);
355        sm_info->SetString(kInstallPreviousVersion, old_version.GetString());
356        sm_info->SetString(kInstallId, extension_id);
357        scoped_ptr<Event> sm_event(
358            new Event(runtime::OnInstalled::kEventName, sm_event_args.Pass()));
359        system->event_router()->DispatchEventWithLazyListener((*i)->id(),
360                                                              sm_event.Pass());
361      }
362    }
363  }
364}
365
366// static
367void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
368    content::BrowserContext* context,
369    const std::string& extension_id,
370    const base::DictionaryValue* manifest) {
371  ExtensionSystem* system = ExtensionSystem::Get(context);
372  if (!system)
373    return;
374
375  scoped_ptr<base::ListValue> args(new base::ListValue);
376  args->Append(manifest->DeepCopy());
377  DCHECK(system->event_router());
378  scoped_ptr<Event> event(
379      new Event(runtime::OnUpdateAvailable::kEventName, args.Pass()));
380  system->event_router()->DispatchEventToExtension(extension_id, event.Pass());
381}
382
383// static
384void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(
385    content::BrowserContext* context) {
386  ExtensionSystem* system = ExtensionSystem::Get(context);
387  if (!system)
388    return;
389
390  scoped_ptr<base::ListValue> args(new base::ListValue);
391  DCHECK(system->event_router());
392  scoped_ptr<Event> event(
393      new Event(runtime::OnBrowserUpdateAvailable::kEventName, args.Pass()));
394  system->event_router()->BroadcastEvent(event.Pass());
395}
396
397// static
398void RuntimeEventRouter::DispatchOnRestartRequiredEvent(
399    content::BrowserContext* context,
400    const std::string& app_id,
401    core_api::runtime::OnRestartRequired::Reason reason) {
402  ExtensionSystem* system = ExtensionSystem::Get(context);
403  if (!system)
404    return;
405
406  scoped_ptr<Event> event(
407      new Event(runtime::OnRestartRequired::kEventName,
408                core_api::runtime::OnRestartRequired::Create(reason)));
409
410  DCHECK(system->event_router());
411  system->event_router()->DispatchEventToExtension(app_id, event.Pass());
412}
413
414// static
415void RuntimeEventRouter::OnExtensionUninstalled(
416    content::BrowserContext* context,
417    const std::string& extension_id) {
418#if defined(ENABLE_EXTENSIONS)
419  GURL uninstall_url(
420      GetUninstallURL(ExtensionPrefs::Get(context), extension_id));
421
422  if (uninstall_url.is_empty())
423    return;
424
425  RuntimeAPI::GetFactoryInstance()->Get(context)->OpenURL(uninstall_url);
426#endif  // defined(ENABLE_EXTENSIONS)
427}
428
429ExtensionFunction::ResponseAction RuntimeGetBackgroundPageFunction::Run() {
430  ExtensionSystem* system = ExtensionSystem::Get(browser_context());
431  ExtensionHost* host =
432      system->process_manager()->GetBackgroundHostForExtension(extension_id());
433  if (system->lazy_background_task_queue()->ShouldEnqueueTask(browser_context(),
434                                                              GetExtension())) {
435    system->lazy_background_task_queue()->AddPendingTask(
436        browser_context(),
437        extension_id(),
438        base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded, this));
439  } else if (host) {
440    OnPageLoaded(host);
441  } else {
442    return RespondNow(Error(kNoBackgroundPageError));
443  }
444
445  return RespondLater();
446}
447
448void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost* host) {
449  if (host) {
450    Respond(NoArguments());
451  } else {
452    Respond(Error(kPageLoadError));
453  }
454}
455
456ExtensionFunction::ResponseAction RuntimeSetUninstallURLFunction::Run() {
457  std::string url_string;
458  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &url_string));
459
460  GURL url(url_string);
461  if (!url.is_valid()) {
462    return RespondNow(
463        Error(ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string)));
464  }
465  SetUninstallURL(
466      ExtensionPrefs::Get(browser_context()), extension_id(), url_string);
467  return RespondNow(NoArguments());
468}
469
470ExtensionFunction::ResponseAction RuntimeReloadFunction::Run() {
471  RuntimeAPI::GetFactoryInstance()->Get(browser_context())->ReloadExtension(
472      extension_id());
473  return RespondNow(NoArguments());
474}
475
476ExtensionFunction::ResponseAction RuntimeRequestUpdateCheckFunction::Run() {
477  if (!RuntimeAPI::GetFactoryInstance()
478           ->Get(browser_context())
479           ->CheckForUpdates(
480               extension_id(),
481               base::Bind(&RuntimeRequestUpdateCheckFunction::CheckComplete,
482                          this))) {
483    return RespondNow(Error(kUpdatesDisabledError));
484  }
485  return RespondLater();
486}
487
488void RuntimeRequestUpdateCheckFunction::CheckComplete(
489    const RuntimeAPIDelegate::UpdateCheckResult& result) {
490  if (result.success) {
491    base::DictionaryValue* details = new base::DictionaryValue;
492    details->SetString("version", result.version);
493    Respond(TwoArguments(new base::StringValue(result.response), details));
494  } else {
495    // HMM(kalman): Why does !success not imply Error()?
496    Respond(OneArgument(new base::StringValue(result.response)));
497  }
498}
499
500ExtensionFunction::ResponseAction RuntimeRestartFunction::Run() {
501  std::string message;
502  bool result =
503      RuntimeAPI::GetFactoryInstance()->Get(browser_context())->RestartDevice(
504          &message);
505  if (!result) {
506    return RespondNow(Error(message));
507  }
508  return RespondNow(NoArguments());
509}
510
511ExtensionFunction::ResponseAction RuntimeGetPlatformInfoFunction::Run() {
512  runtime::PlatformInfo info;
513  if (!RuntimeAPI::GetFactoryInstance()
514           ->Get(browser_context())
515           ->GetPlatformInfo(&info)) {
516    return RespondNow(Error(kPlatformInfoUnavailable));
517  }
518  return RespondNow(
519      ArgumentList(runtime::GetPlatformInfo::Results::Create(info)));
520}
521
522ExtensionFunction::ResponseAction
523RuntimeGetPackageDirectoryEntryFunction::Run() {
524  fileapi::IsolatedContext* isolated_context =
525      fileapi::IsolatedContext::GetInstance();
526  DCHECK(isolated_context);
527
528  std::string relative_path = kPackageDirectoryPath;
529  base::FilePath path = extension_->path();
530  std::string filesystem_id = isolated_context->RegisterFileSystemForPath(
531      fileapi::kFileSystemTypeNativeLocal, std::string(), path, &relative_path);
532
533  int renderer_id = render_view_host_->GetProcess()->GetID();
534  content::ChildProcessSecurityPolicy* policy =
535      content::ChildProcessSecurityPolicy::GetInstance();
536  policy->GrantReadFileSystem(renderer_id, filesystem_id);
537  base::DictionaryValue* dict = new base::DictionaryValue();
538  dict->SetString("fileSystemId", filesystem_id);
539  dict->SetString("baseName", relative_path);
540  return RespondNow(OneArgument(dict));
541}
542
543}  // namespace extensions
544