diff options
author | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-07 14:26:50 +0000 |
---|---|---|
committer | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-07 14:26:50 +0000 |
commit | f0e9ac556da2f86af734a1370bb75dfd050f6f1b (patch) | |
tree | 8f5417b6ba9109ceac04a038ff52370df5e16b73 /extensions | |
parent | 5a65d3045cef8c50eca3649eafc1e867203ade92 (diff) | |
download | chromium_src-f0e9ac556da2f86af734a1370bb75dfd050f6f1b.zip chromium_src-f0e9ac556da2f86af734a1370bb75dfd050f6f1b.tar.gz chromium_src-f0e9ac556da2f86af734a1370bb75dfd050f6f1b.tar.bz2 |
Move chrome.runtime to //extensions.
This moves the runtime API schema and implementation to //extensions,
offloading some of its responsibilities out to a new RuntimeAPIDelegate
provided by the active ExtensionsBrowserClient.
BUG=369391
TBR=derat for mechanical chromeos changes
TBR=tzik for +webkit/browser/fileapi to extensions/browser/DEPS
TBR=sky for new chrome/test dependency
Review URL: https://codereview.chromium.org/264743014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@268749 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/DEPS | 5 | ||||
-rw-r--r-- | extensions/browser/DEPS | 1 | ||||
-rw-r--r-- | extensions/browser/api/runtime/runtime_api.cc | 517 | ||||
-rw-r--r-- | extensions/browser/api/runtime/runtime_api.h | 206 | ||||
-rw-r--r-- | extensions/browser/api/runtime/runtime_api_delegate.cc | 16 | ||||
-rw-r--r-- | extensions/browser/api/runtime/runtime_api_delegate.h | 77 | ||||
-rw-r--r-- | extensions/browser/api/runtime/runtime_apitest.cc | 127 | ||||
-rw-r--r-- | extensions/browser/browser_context_keyed_service_factories.cc | 8 | ||||
-rw-r--r-- | extensions/browser/extensions_browser_client.h | 7 | ||||
-rw-r--r-- | extensions/browser/test_extensions_browser_client.cc | 7 | ||||
-rw-r--r-- | extensions/browser/test_extensions_browser_client.h | 2 | ||||
-rw-r--r-- | extensions/browser/test_runtime_api_delegate.cc | 53 | ||||
-rw-r--r-- | extensions/browser/test_runtime_api_delegate.h | 36 | ||||
-rw-r--r-- | extensions/common/api/_api_features.json | 53 | ||||
-rw-r--r-- | extensions/common/api/_permission_features.json | 6 | ||||
-rw-r--r-- | extensions/common/api/api.gyp | 1 | ||||
-rw-r--r-- | extensions/common/api/runtime.json | 482 | ||||
-rw-r--r-- | extensions/extensions.gyp | 10 |
18 files changed, 1611 insertions, 3 deletions
diff --git a/extensions/DEPS b/extensions/DEPS index 11611da..9d6a6a0 100644 --- a/extensions/DEPS +++ b/extensions/DEPS @@ -26,16 +26,21 @@ specific_include_rules = { # Temporarily allowed testing includes. See above. # TODO(jamescook): Remove these. http://crbug.com/162530 + "+chrome/browser/apps/app_browsertest_util.h", + "+chrome/browser/extensions/api/management/management_api.h", "+chrome/browser/extensions/api/permissions/permissions_api.h", "+chrome/browser/extensions/extension_api_unittest.h", "+chrome/browser/extensions/extension_apitest.h", + "+chrome/browser/extensions/extension_function_test_utils.h", "+chrome/browser/extensions/extension_service_unittest.h", + "+chrome/browser/extensions/test_extension_dir.h", "+chrome/browser/extensions/test_extension_system.h", "+chrome/browser/ui/browser.h", "+chrome/common/chrome_paths.h", "+chrome/common/extensions/features/feature_channel.h", "+chrome/common/extensions/manifest_tests/extension_manifest_test.h", "+chrome/test/base/testing_profile.h", + "+chrome/test/base/ui_test_utils.h", ], "(simple|complex)_feature_unittest\.cc|base_feature_provider_unittest\.cc": [ "+chrome/common/extensions/features/chrome_channel_feature_filter.h", diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS index dfc7fe1..af6e02ab 100644 --- a/extensions/browser/DEPS +++ b/extensions/browser/DEPS @@ -7,4 +7,5 @@ include_rules = [ "+sync", "+third_party/leveldatabase", "+third_party/skia/include", + "+webkit/browser/fileapi", ] diff --git a/extensions/browser/api/runtime/runtime_api.cc b/extensions/browser/api/runtime/runtime_api.cc new file mode 100644 index 0000000..942c3a60d --- /dev/null +++ b/extensions/browser/api/runtime/runtime_api.cc @@ -0,0 +1,517 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/runtime/runtime_api.h" + +#include <utility> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/values.h" +#include "base/version.h" +#include "chrome/browser/chrome_notification_types.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "extensions/browser/api/runtime/runtime_api_delegate.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_host.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/extensions_browser_client.h" +#include "extensions/browser/lazy_background_task_queue.h" +#include "extensions/browser/process_manager.h" +#include "extensions/common/api/runtime.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/extension.h" +#include "extensions/common/manifest_handlers/background_info.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/isolated_context.h" + +using content::BrowserContext; + +namespace extensions { + +namespace runtime = core_api::runtime; + +namespace { + +const char kNoBackgroundPageError[] = "You do not have a background page."; +const char kPageLoadError[] = "Background page failed to load."; +const char kInstallReason[] = "reason"; +const char kInstallReasonChromeUpdate[] = "chrome_update"; +const char kInstallReasonUpdate[] = "update"; +const char kInstallReasonInstall[] = "install"; +const char kInstallPreviousVersion[] = "previousVersion"; +const char kInvalidUrlError[] = "Invalid URL."; +const char kPlatformInfoUnavailable[] = "Platform information unavailable."; + +const char kUpdatesDisabledError[] = "Autoupdate is not enabled."; + +// A preference key storing the url loaded when an extension is uninstalled. +const char kUninstallUrl[] = "uninstall_url"; + +// The name of the directory to be returned by getPackageDirectoryEntry. This +// particular value does not matter to user code, but is chosen for consistency +// with the equivalent Pepper API. +const char kPackageDirectoryPath[] = "crxfs"; + +void DispatchOnStartupEventImpl(BrowserContext* browser_context, + const std::string& extension_id, + bool first_call, + ExtensionHost* host) { + // A NULL host from the LazyBackgroundTaskQueue means the page failed to + // load. Give up. + if (!host && !first_call) + return; + + // Don't send onStartup events to incognito browser contexts. + if (browser_context->IsOffTheRecord()) + return; + + if (ExtensionsBrowserClient::Get()->IsShuttingDown() || + !ExtensionsBrowserClient::Get()->IsValidContext(browser_context)) + return; + ExtensionSystem* system = ExtensionSystem::Get(browser_context); + if (!system) + return; + + // If this is a persistent background page, we want to wait for it to load + // (it might not be ready, since this is startup). But only enqueue once. + // If it fails to load the first time, don't bother trying again. + const Extension* extension = + ExtensionRegistry::Get(browser_context)->enabled_extensions().GetByID( + extension_id); + if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) && + first_call && + system->lazy_background_task_queue()->ShouldEnqueueTask(browser_context, + extension)) { + system->lazy_background_task_queue()->AddPendingTask( + browser_context, + extension_id, + base::Bind( + &DispatchOnStartupEventImpl, browser_context, extension_id, false)); + return; + } + + scoped_ptr<base::ListValue> event_args(new base::ListValue()); + scoped_ptr<Event> event( + new Event(runtime::OnStartup::kEventName, event_args.Pass())); + system->event_router()->DispatchEventToExtension(extension_id, event.Pass()); +} + +void SetUninstallURL(ExtensionPrefs* prefs, + const std::string& extension_id, + const std::string& url_string) { + prefs->UpdateExtensionPref( + extension_id, kUninstallUrl, new base::StringValue(url_string)); +} + +#if defined(ENABLE_EXTENSIONS) +std::string GetUninstallURL(ExtensionPrefs* prefs, + const std::string& extension_id) { + std::string url_string; + prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string); + return url_string; +} +#endif // defined(ENABLE_EXTENSIONS) + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// + +static base::LazyInstance<BrowserContextKeyedAPIFactory<RuntimeAPI> > + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() { + return g_factory.Pointer(); +} + +RuntimeAPI::RuntimeAPI(content::BrowserContext* context) + : browser_context_(context), dispatch_chrome_updated_event_(false) { + registrar_.Add(this, + chrome::NOTIFICATION_EXTENSIONS_READY, + content::Source<BrowserContext>(context)); + registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, + content::Source<BrowserContext>(context)); + registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_INSTALLED, + content::Source<BrowserContext>(context)); + registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_UNINSTALLED, + content::Source<BrowserContext>(context)); + + delegate_ = ExtensionsBrowserClient::Get()->CreateRuntimeAPIDelegate( + browser_context_); + + // Check if registered events are up-to-date. We can only do this once + // per browser context, since it updates internal state when called. + dispatch_chrome_updated_event_ = + ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_); +} + +RuntimeAPI::~RuntimeAPI() { + delegate_->RemoveUpdateObserver(this); +} + +void RuntimeAPI::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_EXTENSIONS_READY: { + OnExtensionsReady(); + break; + } + case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: { + const Extension* extension = + content::Details<const Extension>(details).ptr(); + OnExtensionLoaded(extension); + break; + } + case chrome::NOTIFICATION_EXTENSION_INSTALLED: { + const Extension* extension = + content::Details<const InstalledExtensionInfo>(details)->extension; + OnExtensionInstalled(extension); + break; + } + case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: { + const Extension* extension = + content::Details<const Extension>(details).ptr(); + OnExtensionUninstalled(extension); + break; + } + default: + NOTREACHED(); + break; + } +} + +void RuntimeAPI::OnExtensionsReady() { + // We're done restarting Chrome after an update. + dispatch_chrome_updated_event_ = false; + + delegate_->AddUpdateObserver(this); + + // RuntimeAPI is redirected in incognito, so |browser_context_| is never + // incognito. We don't observe incognito ProcessManagers but that is OK + // because we don't send onStartup events to incognito browser contexts. + DCHECK(!browser_context_->IsOffTheRecord()); + // Some tests use partially constructed Profiles without a process manager. + ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_); + if (extension_system->process_manager()) + extension_system->process_manager()->AddObserver(this); +} + +void RuntimeAPI::OnExtensionLoaded(const Extension* extension) { + if (!dispatch_chrome_updated_event_) + return; + + // Dispatch the onInstalled event with reason "chrome_update". + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent, + browser_context_, + extension->id(), + Version(), + true)); +} + +void RuntimeAPI::OnExtensionInstalled(const Extension* extension) { + // Ephemeral apps are not considered to be installed and do not receive + // the onInstalled() event. + if (extension->is_ephemeral()) + return; + + Version old_version = delegate_->GetPreviousExtensionVersion(extension); + + // Dispatch the onInstalled event. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent, + browser_context_, + extension->id(), + old_version, + false)); +} + +void RuntimeAPI::OnExtensionUninstalled(const Extension* extension) { + // Ephemeral apps are not considered to be installed, so the uninstall URL + // is not invoked when they are removed. + if (extension->is_ephemeral()) + return; + + RuntimeEventRouter::OnExtensionUninstalled(browser_context_, extension->id()); +} + +void RuntimeAPI::Shutdown() { + // ExtensionSystem deletes its ProcessManager during the Shutdown() phase, so + // the observer must be removed here and not in the RuntimeAPI destructor. + ProcessManager* process_manager = + ExtensionSystem::Get(browser_context_)->process_manager(); + // Some tests use partially constructed Profiles without a process manager. + if (process_manager) + process_manager->RemoveObserver(this); +} + +void RuntimeAPI::OnAppUpdateAvailable(const Extension* extension) { + RuntimeEventRouter::DispatchOnUpdateAvailableEvent( + browser_context_, extension->id(), extension->manifest()->value()); +} + +void RuntimeAPI::OnChromeUpdateAvailable() { + RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(browser_context_); +} + +void RuntimeAPI::OnBackgroundHostStartup(const Extension* extension) { + RuntimeEventRouter::DispatchOnStartupEvent(browser_context_, extension->id()); +} + +void RuntimeAPI::ReloadExtension(const std::string& extension_id) { + delegate_->ReloadExtension(extension_id); +} + +bool RuntimeAPI::CheckForUpdates( + const std::string& extension_id, + const RuntimeAPIDelegate::UpdateCheckCallback& callback) { + return delegate_->CheckForUpdates(extension_id, callback); +} + +void RuntimeAPI::OpenURL(const GURL& update_url) { + delegate_->OpenURL(update_url); +} + +bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo* info) { + return delegate_->GetPlatformInfo(info); +} + +bool RuntimeAPI::RestartDevice(std::string* error_message) { + return delegate_->RestartDevice(error_message); +} + +/////////////////////////////////////////////////////////////////////////////// + +// static +void RuntimeEventRouter::DispatchOnStartupEvent( + content::BrowserContext* context, + const std::string& extension_id) { + DispatchOnStartupEventImpl(context, extension_id, true, NULL); +} + +// static +void RuntimeEventRouter::DispatchOnInstalledEvent( + content::BrowserContext* context, + const std::string& extension_id, + const Version& old_version, + bool chrome_updated) { + if (!ExtensionsBrowserClient::Get()->IsValidContext(context)) + return; + ExtensionSystem* system = ExtensionSystem::Get(context); + if (!system) + return; + + scoped_ptr<base::ListValue> event_args(new base::ListValue()); + base::DictionaryValue* info = new base::DictionaryValue(); + event_args->Append(info); + if (old_version.IsValid()) { + info->SetString(kInstallReason, kInstallReasonUpdate); + info->SetString(kInstallPreviousVersion, old_version.GetString()); + } else if (chrome_updated) { + info->SetString(kInstallReason, kInstallReasonChromeUpdate); + } else { + info->SetString(kInstallReason, kInstallReasonInstall); + } + DCHECK(system->event_router()); + scoped_ptr<Event> event( + new Event(runtime::OnInstalled::kEventName, event_args.Pass())); + system->event_router()->DispatchEventWithLazyListener(extension_id, + event.Pass()); +} + +// static +void RuntimeEventRouter::DispatchOnUpdateAvailableEvent( + content::BrowserContext* context, + const std::string& extension_id, + const base::DictionaryValue* manifest) { + ExtensionSystem* system = ExtensionSystem::Get(context); + if (!system) + return; + + scoped_ptr<base::ListValue> args(new base::ListValue); + args->Append(manifest->DeepCopy()); + DCHECK(system->event_router()); + scoped_ptr<Event> event( + new Event(runtime::OnUpdateAvailable::kEventName, args.Pass())); + system->event_router()->DispatchEventToExtension(extension_id, event.Pass()); +} + +// static +void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent( + content::BrowserContext* context) { + ExtensionSystem* system = ExtensionSystem::Get(context); + if (!system) + return; + + scoped_ptr<base::ListValue> args(new base::ListValue); + DCHECK(system->event_router()); + scoped_ptr<Event> event( + new Event(runtime::OnBrowserUpdateAvailable::kEventName, args.Pass())); + system->event_router()->BroadcastEvent(event.Pass()); +} + +// static +void RuntimeEventRouter::DispatchOnRestartRequiredEvent( + content::BrowserContext* context, + const std::string& app_id, + core_api::runtime::OnRestartRequired::Reason reason) { + ExtensionSystem* system = ExtensionSystem::Get(context); + if (!system) + return; + + scoped_ptr<Event> event( + new Event(runtime::OnRestartRequired::kEventName, + core_api::runtime::OnRestartRequired::Create(reason))); + + DCHECK(system->event_router()); + system->event_router()->DispatchEventToExtension(app_id, event.Pass()); +} + +// static +void RuntimeEventRouter::OnExtensionUninstalled( + content::BrowserContext* context, + const std::string& extension_id) { +#if defined(ENABLE_EXTENSIONS) + GURL uninstall_url( + GetUninstallURL(ExtensionPrefs::Get(context), extension_id)); + + if (uninstall_url.is_empty()) + return; + + RuntimeAPI::GetFactoryInstance()->Get(context)->OpenURL(uninstall_url); +#endif // defined(ENABLE_EXTENSIONS) +} + +ExtensionFunction::ResponseAction RuntimeGetBackgroundPageFunction::Run() { + ExtensionSystem* system = ExtensionSystem::Get(browser_context()); + ExtensionHost* host = + system->process_manager()->GetBackgroundHostForExtension(extension_id()); + if (system->lazy_background_task_queue()->ShouldEnqueueTask(browser_context(), + GetExtension())) { + system->lazy_background_task_queue()->AddPendingTask( + browser_context(), + extension_id(), + base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded, this)); + } else if (host) { + OnPageLoaded(host); + } else { + return RespondNow(Error(kNoBackgroundPageError)); + } + + return RespondLater(); +} + +void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost* host) { + if (host) { + Respond(NoArguments()); + } else { + Respond(Error(kPageLoadError)); + } +} + +ExtensionFunction::ResponseAction RuntimeSetUninstallURLFunction::Run() { + std::string url_string; + EXTENSION_FUNCTION_VALIDATE_TYPESAFE(args_->GetString(0, &url_string)); + + GURL url(url_string); + if (!url.is_valid()) { + return RespondNow( + Error(ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string))); + } + SetUninstallURL( + ExtensionPrefs::Get(browser_context()), extension_id(), url_string); + return RespondNow(NoArguments()); +} + +ExtensionFunction::ResponseAction RuntimeReloadFunction::Run() { + RuntimeAPI::GetFactoryInstance()->Get(browser_context())->ReloadExtension( + extension_id()); + return RespondNow(NoArguments()); +} + +ExtensionFunction::ResponseAction RuntimeRequestUpdateCheckFunction::Run() { + if (!RuntimeAPI::GetFactoryInstance() + ->Get(browser_context()) + ->CheckForUpdates( + extension_id(), + base::Bind(&RuntimeRequestUpdateCheckFunction::CheckComplete, + this))) { + return RespondNow(Error(kUpdatesDisabledError)); + } + return RespondLater(); +} + +void RuntimeRequestUpdateCheckFunction::CheckComplete( + const RuntimeAPIDelegate::UpdateCheckResult& result) { + if (result.success) { + base::ListValue* results = new base::ListValue; + results->AppendString(result.response); + base::DictionaryValue* details = new base::DictionaryValue; + results->Append(details); + details->SetString("version", result.version); + Respond(MultipleArguments(results)); + } else { + Respond(SingleArgument(new base::StringValue(result.response))); + } +} + +ExtensionFunction::ResponseAction RuntimeRestartFunction::Run() { + std::string message; + bool result = + RuntimeAPI::GetFactoryInstance()->Get(browser_context())->RestartDevice( + &message); + if (!result) { + return RespondNow(Error(message)); + } + return RespondNow(NoArguments()); +} + +ExtensionFunction::ResponseAction RuntimeGetPlatformInfoFunction::Run() { + runtime::PlatformInfo info; + if (!RuntimeAPI::GetFactoryInstance() + ->Get(browser_context()) + ->GetPlatformInfo(&info)) { + return RespondNow(Error(kPlatformInfoUnavailable)); + } + return RespondNow(MultipleArguments( + runtime::GetPlatformInfo::Results::Create(info).release())); +} + +ExtensionFunction::ResponseAction +RuntimeGetPackageDirectoryEntryFunction::Run() { + fileapi::IsolatedContext* isolated_context = + fileapi::IsolatedContext::GetInstance(); + DCHECK(isolated_context); + + std::string relative_path = kPackageDirectoryPath; + base::FilePath path = extension_->path(); + std::string filesystem_id = isolated_context->RegisterFileSystemForPath( + fileapi::kFileSystemTypeNativeLocal, path, &relative_path); + + int renderer_id = render_view_host_->GetProcess()->GetID(); + content::ChildProcessSecurityPolicy* policy = + content::ChildProcessSecurityPolicy::GetInstance(); + policy->GrantReadFileSystem(renderer_id, filesystem_id); + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("fileSystemId", filesystem_id); + dict->SetString("baseName", relative_path); + return RespondNow(SingleArgument(dict)); +} + +} // namespace extensions diff --git a/extensions/browser/api/runtime/runtime_api.h b/extensions/browser/api/runtime/runtime_api.h new file mode 100644 index 0000000..e341398 --- /dev/null +++ b/extensions/browser/api/runtime/runtime_api.h @@ -0,0 +1,206 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_H_ +#define EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_H_ + +#include <string> + +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "extensions/browser/api/runtime/runtime_api_delegate.h" +#include "extensions/browser/browser_context_keyed_api_factory.h" +#include "extensions/browser/extension_function.h" +#include "extensions/browser/process_manager_observer.h" +#include "extensions/browser/update_observer.h" +#include "extensions/common/api/runtime.h" + +namespace base { +class Version; +} + +namespace content { +class BrowserContext; +} + +namespace extensions { + +namespace core_api { +namespace runtime { +struct PlatformInfo; +} +} + +class Extension; +class ExtensionHost; + +// Runtime API dispatches onStartup, onInstalled, and similar events to +// extensions. There is one instance shared between a browser context and +// its related incognito instance. +class RuntimeAPI : public BrowserContextKeyedAPI, + public content::NotificationObserver, + public UpdateObserver, + public ProcessManagerObserver { + public: + static BrowserContextKeyedAPIFactory<RuntimeAPI>* GetFactoryInstance(); + + explicit RuntimeAPI(content::BrowserContext* context); + virtual ~RuntimeAPI(); + + // content::NotificationObserver overrides: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + void ReloadExtension(const std::string& extension_id); + bool CheckForUpdates(const std::string& extension_id, + const RuntimeAPIDelegate::UpdateCheckCallback& callback); + void OpenURL(const GURL& uninstall_url); + bool GetPlatformInfo(core_api::runtime::PlatformInfo* info); + bool RestartDevice(std::string* error_message); + + private: + friend class BrowserContextKeyedAPIFactory<RuntimeAPI>; + + void OnExtensionsReady(); + void OnExtensionLoaded(const Extension* extension); + void OnExtensionInstalled(const Extension* extension); + void OnExtensionUninstalled(const Extension* extension); + + // BrowserContextKeyedAPI implementation: + static const char* service_name() { return "RuntimeAPI"; } + static const bool kServiceRedirectedInIncognito = true; + static const bool kServiceIsNULLWhileTesting = true; + virtual void Shutdown() OVERRIDE; + + // extensions::UpdateObserver overrides: + virtual void OnAppUpdateAvailable(const Extension* extension) OVERRIDE; + virtual void OnChromeUpdateAvailable() OVERRIDE; + + // ProcessManagerObserver implementation: + virtual void OnBackgroundHostStartup(const Extension* extension) OVERRIDE; + + scoped_ptr<RuntimeAPIDelegate> delegate_; + + content::BrowserContext* browser_context_; + + // True if we should dispatch the chrome.runtime.onInstalled event with + // reason "chrome_update" upon loading each extension. + bool dispatch_chrome_updated_event_; + + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(RuntimeAPI); +}; + +class RuntimeEventRouter { + public: + // Dispatches the onStartup event to all currently-loaded extensions. + static void DispatchOnStartupEvent(content::BrowserContext* context, + const std::string& extension_id); + + // Dispatches the onInstalled event to the given extension. + static void DispatchOnInstalledEvent(content::BrowserContext* context, + const std::string& extension_id, + const base::Version& old_version, + bool chrome_updated); + + // Dispatches the onUpdateAvailable event to the given extension. + static void DispatchOnUpdateAvailableEvent( + content::BrowserContext* context, + const std::string& extension_id, + const base::DictionaryValue* manifest); + + // Dispatches the onBrowserUpdateAvailable event to all extensions. + static void DispatchOnBrowserUpdateAvailableEvent( + content::BrowserContext* context); + + // Dispatches the onRestartRequired event to the given app. + static void DispatchOnRestartRequiredEvent( + content::BrowserContext* context, + const std::string& app_id, + core_api::runtime::OnRestartRequired::Reason reason); + + // Does any work needed at extension uninstall (e.g. load uninstall url). + static void OnExtensionUninstalled(content::BrowserContext* context, + const std::string& extension_id); +}; + +class RuntimeGetBackgroundPageFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("runtime.getBackgroundPage", + RUNTIME_GETBACKGROUNDPAGE) + + protected: + virtual ~RuntimeGetBackgroundPageFunction() {} + virtual ResponseAction Run() OVERRIDE; + + private: + void OnPageLoaded(ExtensionHost*); +}; + +class RuntimeSetUninstallURLFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("runtime.setUninstallURL", RUNTIME_SETUNINSTALLURL) + + protected: + virtual ~RuntimeSetUninstallURLFunction() {} + virtual ResponseAction Run() OVERRIDE; +}; + +class RuntimeReloadFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("runtime.reload", RUNTIME_RELOAD) + + protected: + virtual ~RuntimeReloadFunction() {} + virtual ResponseAction Run() OVERRIDE; +}; + +class RuntimeRequestUpdateCheckFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("runtime.requestUpdateCheck", + RUNTIME_REQUESTUPDATECHECK) + + protected: + virtual ~RuntimeRequestUpdateCheckFunction() {} + virtual ResponseAction Run() OVERRIDE; + + private: + void CheckComplete(const RuntimeAPIDelegate::UpdateCheckResult& result); +}; + +class RuntimeRestartFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("runtime.restart", RUNTIME_RESTART) + + protected: + virtual ~RuntimeRestartFunction() {} + virtual ResponseAction Run() OVERRIDE; +}; + +class RuntimeGetPlatformInfoFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("runtime.getPlatformInfo", + RUNTIME_GETPLATFORMINFO); + + protected: + virtual ~RuntimeGetPlatformInfoFunction() {} + virtual ResponseAction Run() OVERRIDE; +}; + +class RuntimeGetPackageDirectoryEntryFunction + : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("runtime.getPackageDirectoryEntry", + RUNTIME_GETPACKAGEDIRECTORYENTRY) + + protected: + virtual ~RuntimeGetPackageDirectoryEntryFunction() {} + virtual ResponseAction Run() OVERRIDE; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_H_ diff --git a/extensions/browser/api/runtime/runtime_api_delegate.cc b/extensions/browser/api/runtime/runtime_api_delegate.cc new file mode 100644 index 0000000..3ead8b6 --- /dev/null +++ b/extensions/browser/api/runtime/runtime_api_delegate.cc @@ -0,0 +1,16 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/runtime/runtime_api_delegate.h" + +namespace extensions { + +RuntimeAPIDelegate::UpdateCheckResult::UpdateCheckResult( + bool success, + const std::string& response, + const std::string& version) + : success(success), response(response), version(version) { +} + +} // namespace extensions diff --git a/extensions/browser/api/runtime/runtime_api_delegate.h b/extensions/browser/api/runtime/runtime_api_delegate.h new file mode 100644 index 0000000..98b4963 --- /dev/null +++ b/extensions/browser/api/runtime/runtime_api_delegate.h @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_DELEGATE_H +#define EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_DELEGATE_H + +#include "base/callback.h" +#include "base/version.h" + +class GURL; + +namespace extensions { + +namespace core_api { +namespace runtime { +struct PlatformInfo; +} +} + +class Extension; +class UpdateObserver; + +// This is a delegate interface for chrome.runtime API behavior. Clients must +// vend some implementation of this interface through +// ExtensionsBrowserClient::CreateRuntimeAPIDelegate. +class RuntimeAPIDelegate { + public: + struct UpdateCheckResult { + bool success; + std::string response; + std::string version; + + UpdateCheckResult(bool success, + const std::string& response, + const std::string& version); + }; + + virtual ~RuntimeAPIDelegate() {} + + // The callback given to RequestUpdateCheck. + typedef base::Callback<void(const UpdateCheckResult&)> UpdateCheckCallback; + + // Registers an UpdateObserver on behalf of the runtime API. + virtual void AddUpdateObserver(UpdateObserver* observer) = 0; + + // Unregisters an UpdateObserver on behalf of the runtime API. + virtual void RemoveUpdateObserver(UpdateObserver* observer) = 0; + + // Determines an extension's previously installed version if applicable. + virtual base::Version GetPreviousExtensionVersion( + const Extension* extension) = 0; + + // Reloads an extension. + virtual void ReloadExtension(const std::string& extension_id) = 0; + + // Requests an extensions update update check. Returns |false| if updates + // are disabled. Otherwise |callback| is called with the result of the + // update check. + virtual bool CheckForUpdates(const std::string& extension_id, + const UpdateCheckCallback& callback) = 0; + + // Navigates the browser to a URL on behalf of the runtime API. + virtual void OpenURL(const GURL& uninstall_url) = 0; + + // Populates platform info to be provided by the getPlatformInfo function. + // Returns false iff no info is provided. + virtual bool GetPlatformInfo(core_api::runtime::PlatformInfo* info) = 0; + + // Request a restart of the host device. Returns false iff the device + // will not be restarted. + virtual bool RestartDevice(std::string* error_message) = 0; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_RUNTIME_RUNTIME_API_DELEGATE_H diff --git a/extensions/browser/api/runtime/runtime_apitest.cc b/extensions/browser/api/runtime/runtime_apitest.cc new file mode 100644 index 0000000..6fd81f1 --- /dev/null +++ b/extensions/browser/api/runtime/runtime_apitest.cc @@ -0,0 +1,127 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/apps/app_browsertest_util.h" +#include "chrome/browser/extensions/api/management/management_api.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/test_extension_dir.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/notification_service.h" +#include "extensions/browser/api/runtime/runtime_api.h" +#include "extensions/browser/extension_registry.h" +#include "net/test/embedded_test_server/embedded_test_server.h" + +// Tests the privileged components of chrome.runtime. +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimePrivileged) { + ASSERT_TRUE(RunExtensionTest("runtime/privileged")) << message_; +} + +// Tests the unprivileged components of chrome.runtime. +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeUnprivileged) { + ASSERT_TRUE(StartEmbeddedTestServer()); + ASSERT_TRUE( + LoadExtension(test_data_dir_.AppendASCII("runtime/content_script"))); + + // The content script runs on webpage.html. + ResultCatcher catcher; + ui_test_utils::NavigateToURL(browser(), + embedded_test_server()->GetURL("/webpage.html")); + EXPECT_TRUE(catcher.GetNextResult()) << message_; +} + +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeUninstallURL) { + // Auto-confirm the uninstall dialog. + extensions::ManagementUninstallFunction::SetAutoConfirmForTest(true); + ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("runtime") + .AppendASCII("uninstall_url") + .AppendASCII("sets_uninstall_url"))); + ASSERT_TRUE(RunExtensionTest("runtime/uninstall_url")) << message_; +} + +namespace extensions { + +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeRuntimeGetPlatformInfo) { + scoped_ptr<base::Value> result( + extension_function_test_utils::RunFunctionAndReturnSingleResult( + new RuntimeGetPlatformInfoFunction(), "[]", browser())); + ASSERT_TRUE(result.get() != NULL); + base::DictionaryValue* dict = + extension_function_test_utils::ToDictionary(result.get()); + ASSERT_TRUE(dict != NULL); + EXPECT_TRUE(dict->HasKey("os")); + EXPECT_TRUE(dict->HasKey("arch")); + EXPECT_TRUE(dict->HasKey("nacl_arch")); +} + +// Tests chrome.runtime.getPackageDirectory with an app. +IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, + ChromeRuntimeGetPackageDirectoryEntryApp) { + ClearCommandLineArgs(); + ASSERT_TRUE(RunPlatformAppTest("api_test/runtime/get_package_directory/app")) + << message_; +} + +// Tests chrome.runtime.getPackageDirectory with an extension. +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, + ChromeRuntimeGetPackageDirectoryEntryExtension) { + ASSERT_TRUE(RunExtensionTest("runtime/get_package_directory/extension")) + << message_; +} + +// Tests chrome.runtime.reload +// This test is flaky on Linux: crbug.com/366181 +#if defined(OS_LINUX) || defined(OS_CHROMEOS) +#define MAYBE_ChromeRuntimeReload DISABLED_ChromeRuntimeReload +#else +#define MAYBE_ChromeRuntimeReload ChromeRuntimeReload +#endif +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_ChromeRuntimeReload) { + ExtensionRegistry* registry = ExtensionRegistry::Get(profile()); + const char kManifest[] = + "{" + " \"name\": \"reload\"," + " \"version\": \"1.0\"," + " \"background\": {" + " \"scripts\": [\"background.js\"]" + " }," + " \"manifest_version\": 2" + "}"; + + TestExtensionDir dir; + dir.WriteManifest(kManifest); + dir.WriteFile(FILE_PATH_LITERAL("background.js"), "console.log('loaded');"); + + const Extension* extension = LoadExtension(dir.unpacked_path()); + ASSERT_TRUE(extension); + const std::string extension_id = extension->id(); + + // Somewhat arbitrary upper limit of 30 iterations. If the extension manages + // to reload itself that often without being terminated, the test fails + // anyway. + for (int i = 0; i < 30; i++) { + content::WindowedNotificationObserver unload_observer( + chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, + content::NotificationService::AllSources()); + content::WindowedNotificationObserver load_observer( + chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, + content::NotificationService::AllSources()); + + ASSERT_TRUE(ExecuteScriptInBackgroundPageNoWait( + extension_id, "chrome.runtime.reload();")); + unload_observer.Wait(); + + if (registry->GetExtensionById(extension_id, + ExtensionRegistry::TERMINATED)) { + break; + } else { + load_observer.Wait(); + WaitForExtensionViewsToLoad(); + } + } + ASSERT_TRUE( + registry->GetExtensionById(extension_id, ExtensionRegistry::TERMINATED)); +} + +} // namespace extensions diff --git a/extensions/browser/browser_context_keyed_service_factories.cc b/extensions/browser/browser_context_keyed_service_factories.cc index 38ee253..6091a0f 100644 --- a/extensions/browser/browser_context_keyed_service_factories.cc +++ b/extensions/browser/browser_context_keyed_service_factories.cc @@ -5,6 +5,7 @@ #include "extensions/browser/browser_context_keyed_service_factories.h" #include "extensions/browser/api/api_resource_manager.h" +#include "extensions/browser/api/runtime/runtime_api.h" #include "extensions/browser/api/socket/socket.h" #include "extensions/browser/api/socket/tcp_socket.h" #include "extensions/browser/api/socket/udp_socket.h" @@ -26,9 +27,10 @@ void EnsureBrowserContextKeyedServiceFactoriesBuilt() { core_api::TCPServerSocketEventDispatcher::GetFactoryInstance(); core_api::TCPSocketEventDispatcher::GetFactoryInstance(); core_api::UDPSocketEventDispatcher::GetFactoryInstance(); - extensions::ExtensionPrefsFactory::GetInstance(); - extensions::RendererStartupHelperFactory::GetInstance(); - extensions::StorageFrontend::GetFactoryInstance(); + ExtensionPrefsFactory::GetInstance(); + RendererStartupHelperFactory::GetInstance(); + RuntimeAPI::GetFactoryInstance(); + StorageFrontend::GetFactoryInstance(); } } // namespace extensions diff --git a/extensions/browser/extensions_browser_client.h b/extensions/browser/extensions_browser_client.h index e3b91b2..9356aa6 100644 --- a/extensions/browser/extensions_browser_client.h +++ b/extensions/browser/extensions_browser_client.h @@ -40,6 +40,7 @@ class ExtensionPrefsObserver; class ExtensionSystem; class ExtensionSystemProvider; class InfoMap; +class RuntimeAPIDelegate; // Interface to allow the extensions module to make browser-process-specific // queries of the embedder. Should be Set() once in the browser process. @@ -166,6 +167,12 @@ class ExtensionsBrowserClient { virtual void RegisterExtensionFunctions( ExtensionFunctionRegistry* registry) const = 0; + // Creates a RuntimeAPIDelegate responsible for handling extensions + // management-related events such as update and installation on behalf of the + // core runtime API implementation. + virtual scoped_ptr<RuntimeAPIDelegate> CreateRuntimeAPIDelegate( + content::BrowserContext* context) const = 0; + // Returns the single instance of |this|. static ExtensionsBrowserClient* Get(); diff --git a/extensions/browser/test_extensions_browser_client.cc b/extensions/browser/test_extensions_browser_client.cc index 6e919ea..7708ca7 100644 --- a/extensions/browser/test_extensions_browser_client.cc +++ b/extensions/browser/test_extensions_browser_client.cc @@ -7,6 +7,7 @@ #include "content/public/browser/browser_context.h" #include "extensions/browser/app_sorting.h" #include "extensions/browser/extension_host_delegate.h" +#include "extensions/browser/test_runtime_api_delegate.h" using content::BrowserContext; @@ -155,4 +156,10 @@ TestExtensionsBrowserClient::GetExtensionSystemFactory() { void TestExtensionsBrowserClient::RegisterExtensionFunctions( ExtensionFunctionRegistry* registry) const {} +scoped_ptr<RuntimeAPIDelegate> +TestExtensionsBrowserClient::CreateRuntimeAPIDelegate( + content::BrowserContext* context) const { + return scoped_ptr<RuntimeAPIDelegate>(new TestRuntimeAPIDelegate()); +} + } // namespace extensions diff --git a/extensions/browser/test_extensions_browser_client.h b/extensions/browser/test_extensions_browser_client.h index f7401fe..ff09ae3 100644 --- a/extensions/browser/test_extensions_browser_client.h +++ b/extensions/browser/test_extensions_browser_client.h @@ -73,6 +73,8 @@ class TestExtensionsBrowserClient : public ExtensionsBrowserClient { virtual ExtensionSystemProvider* GetExtensionSystemFactory() OVERRIDE; virtual void RegisterExtensionFunctions( ExtensionFunctionRegistry* registry) const OVERRIDE; + virtual scoped_ptr<RuntimeAPIDelegate> CreateRuntimeAPIDelegate( + content::BrowserContext* context) const OVERRIDE; private: content::BrowserContext* main_context_; // Not owned. diff --git a/extensions/browser/test_runtime_api_delegate.cc b/extensions/browser/test_runtime_api_delegate.cc new file mode 100644 index 0000000..9394050 --- /dev/null +++ b/extensions/browser/test_runtime_api_delegate.cc @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/test_runtime_api_delegate.h" + +#include "extensions/common/api/runtime.h" + +namespace extensions { + +using core_api::runtime::PlatformInfo; + +TestRuntimeAPIDelegate::TestRuntimeAPIDelegate() { +} + +TestRuntimeAPIDelegate::~TestRuntimeAPIDelegate() { +} + +void TestRuntimeAPIDelegate::AddUpdateObserver(UpdateObserver* observer) { +} + +void TestRuntimeAPIDelegate::RemoveUpdateObserver(UpdateObserver* observer) { +} + +base::Version TestRuntimeAPIDelegate::GetPreviousExtensionVersion( + const Extension* extension) { + return base::Version(); +} + +void TestRuntimeAPIDelegate::ReloadExtension(const std::string& extension_id) { +} + +bool TestRuntimeAPIDelegate::CheckForUpdates( + const std::string& extension_id, + const UpdateCheckCallback& callback) { + return false; +} + +void TestRuntimeAPIDelegate::OpenURL(const GURL& uninstall_url) { +} + +bool TestRuntimeAPIDelegate::GetPlatformInfo(PlatformInfo* info) { + // TODO(rockot): This probably isn't right. Maybe this delegate should just + // support manual PlatformInfo override for tests if necessary. + info->os = PlatformInfo::OS_CROS_; + return true; +} + +bool TestRuntimeAPIDelegate::RestartDevice(std::string* error_message) { + return false; +} + +} // namespace extensions diff --git a/extensions/browser/test_runtime_api_delegate.h b/extensions/browser/test_runtime_api_delegate.h new file mode 100644 index 0000000..8922bd5 --- /dev/null +++ b/extensions/browser/test_runtime_api_delegate.h @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXENSIONS_BROWSER_TEST_RUNTIME_API_DELEGATE_H_ +#define EXENSIONS_BROWSER_TEST_RUNTIME_API_DELEGATE_H_ + +#include "base/macros.h" +#include "extensions/browser/api/runtime/runtime_api_delegate.h" + +namespace extensions { + +class TestRuntimeAPIDelegate : public RuntimeAPIDelegate { + public: + TestRuntimeAPIDelegate(); + virtual ~TestRuntimeAPIDelegate(); + + // RuntimeAPIDelegate implementation. + virtual void AddUpdateObserver(UpdateObserver* observer) OVERRIDE; + virtual void RemoveUpdateObserver(UpdateObserver* observer) OVERRIDE; + virtual base::Version GetPreviousExtensionVersion( + const Extension* extension) OVERRIDE; + virtual void ReloadExtension(const std::string& extension_id) OVERRIDE; + virtual bool CheckForUpdates(const std::string& extension_id, + const UpdateCheckCallback& callback) OVERRIDE; + virtual void OpenURL(const GURL& uninstall_url) OVERRIDE; + virtual bool GetPlatformInfo(core_api::runtime::PlatformInfo* info) OVERRIDE; + virtual bool RestartDevice(std::string* error_message) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TestRuntimeAPIDelegate); +}; + +} // namespace extensions + +#endif // EXENSIONS_BROWSER_TEST_RUNTIME_API_DELEGATE_H_ diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json index 4ea9b9e..6fa7d8b 100644 --- a/extensions/common/api/_api_features.json +++ b/extensions/common/api/_api_features.json @@ -14,6 +14,49 @@ "dependencies": ["permission:dns"], "contexts": ["blessed_extension"] }, + "runtime": { + "channel": "stable", + "extension_types": ["extension", "legacy_packaged_app", "platform_app"], + "contexts": ["blessed_extension"] + }, + "runtime.getManifest": { + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, + "runtime.connect": { + "contexts": "all", + "matches": ["<all_urls>"] + }, + "runtime.getURL": { + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, + "runtime.id": { + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, + "runtime.lastError": { + "contexts": "all", + "extension_types": "all", + "matches": ["<all_urls>"] + }, + "runtime.onConnect": { + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, + "runtime.onMessage": { + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, + "runtime.reload": { + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, + "runtime.requestUpdateCheck": { + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, + "runtime.sendMessage": { + "contexts": "all", + "matches": ["<all_urls>"] + }, + "runtime.setUninstallURL": { + "channel": "dev", + "contexts": ["blessed_extension", "unblessed_extension", "content_script"] + }, "socket": { "dependencies": ["permission:socket"], "contexts": ["blessed_extension"] @@ -40,6 +83,16 @@ "extension_types": "all", "contexts": ["blessed_extension", "unblessed_extension", "content_script"] }, + "types": { + "channel": "stable", + "extension_types": ["extension", "legacy_packaged_app", "platform_app"], + "contexts": ["blessed_extension"] + }, + "types.private": { + "channel": "dev", + "extension_types": ["extension"], + "location": "component" + }, "usb": { "dependencies": ["permission:usb"], "contexts": ["blessed_extension"] diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json index 3447aac..4a24afe 100644 --- a/extensions/common/api/_permission_features.json +++ b/extensions/common/api/_permission_features.json @@ -33,6 +33,12 @@ ] } ], + // Note: runtime is not actually a permission, but some systems check these + // values to verify restrictions. + "runtime": { + "channel": "stable", + "extension_types": ["extension", "legacy_packaged_app", "platform_app"] + }, "socket": [ { "channel": "stable", diff --git a/extensions/common/api/api.gyp b/extensions/common/api/api.gyp index b124637..400701d 100644 --- a/extensions/common/api/api.gyp +++ b/extensions/common/api/api.gyp @@ -24,6 +24,7 @@ 'schema_files': [ 'dns.idl', 'extensions_manifest_types.json', + 'runtime.json', 'socket.idl', 'sockets_tcp.idl', 'sockets_tcp_server.idl', diff --git a/extensions/common/api/runtime.json b/extensions/common/api/runtime.json new file mode 100644 index 0000000..6009576 --- /dev/null +++ b/extensions/common/api/runtime.json @@ -0,0 +1,482 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note: Many of these functions and events are implemented by hand and should +// not elicit any code generation from the schema compiler. These items are +// marked "nocompile." +[ + { + "namespace": "runtime", + "description": "Use the <code>chrome.runtime</code> API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.", + "types": [ + { + "id": "Port", + "type": "object", + "nocompile": true, + "description": "An object which allows two way communication with other pages.", + "properties": { + "name": {"type": "string"}, + "disconnect": { "type": "function" }, + "onDisconnect": { "$ref": "events.Event" }, + "onMessage": { "$ref": "events.Event" }, + "postMessage": {"type": "function"}, + "sender": { + "$ref": "MessageSender", + "optional": true, + "description": "This property will <b>only</b> be present on ports passed to onConnect/onConnectExternal listeners." + } + }, + "additionalProperties": { "type": "any"} + }, + { + "id": "MessageSender", + "type": "object", + "nocompile": true, + "description": "An object containing information about the script context that sent a message or request.", + "properties": { + "tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab) which opened the connection, if any. This property will <strong>only</strong> be present when the connection was opened from a tab (including content scripts), and <strong>only</strong> if the receiver is an extension, not an app."}, + "id": {"type": "string", "optional": true, "description": "The ID of the extension or app that opened the connection, if any."}, + "url": {"type": "string", "optional": true, "description": "The URL of the page or frame that opened the connection, if any. This property will <strong>only</strong> be present when the connection was opened from a tab or content script."}, + "tlsChannelId": {"type": "string", "optional": true, "description": "The TLS channel ID of the web page that opened the connection, if requested by the extension or app, and if available."} + } + }, + { + "id": "PlatformInfo", + "type": "object", + "description": "An object containing information about the current platform.", + "properties": { + "os": { + "type": "string", + "description": "The operating system chrome is running on.", + "enum": ["mac", "win", "android", "cros", "linux", "openbsd"] + }, + "arch": { + "type": "string", + "enum": ["arm", "x86-32", "x86-64"], + "description": "The machine's processor architecture." + }, + "nacl_arch" : { + "description": "The native client architecture. This may be different from arch on some platforms.", + "type": "string", + "enum": ["arm", "x86-32", "x86-64"] + } + } + } + ], + "properties": { + "lastError": { + "type": "object", + "optional": true, + "description": "This will be defined during an API method callback if there was an error", + "properties": { + "message": { + "optional": true, + "type": "string", + "description": "Details about the error which occurred." + } + } + }, + "id": { + "type": "string", + "description": "The ID of the extension/app." + } + }, + "functions": [ + { + "name": "getBackgroundPage", + "type": "function", + "description": "Retrieves the JavaScript 'window' object for the background page running inside the current extension/app. If the background page is an event page, the system will ensure it is loaded before calling the callback. If there is no background page, an error is set.", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "backgroundPage", + // Note: Only optional because we don't support validation + // for custom callbacks. + "optional": true, + "type": "object", + "isInstanceOf": "Window", + "additionalProperties": { "type": "any" }, + "description": "The JavaScript 'window' object for the background page." + } + ] + } + ] + }, + { + "name": "getManifest", + "description": "Returns details about the app or extension from the manifest. The object returned is a serialization of the full <a href=\"manifest.html\">manifest file</a>.", + "type": "function", + "nocompile": true, + "parameters": [], + "returns": { + "type": "object", + "properties": {}, + "additionalProperties": { "type": "any" }, + "description": "The manifest details." + } + }, + { + "name": "getURL", + "type": "function", + "nocompile": true, + "description": "Converts a relative path within an app/extension install directory to a fully-qualified URL.", + "parameters": [ + { + "type": "string", + "name": "path", + "description": "A path to a resource within an app/extension expressed relative to its install directory." + } + ], + "returns": { + "type": "string", + "description": "The fully-qualified URL to the resource." + } + }, + { + "name": "setUninstallURL", + "type": "function", + "description": "Sets the URL to be visited upon uninstallation. This may be used to clean up server-side data, do analytics, and implement surveys. Maximum 255 characters.", + "parameters": [ + { + "type": "string", + "name": "url", + "maxLength": 255 + } + ] + }, + { + "name": "reload", + "description": "Reloads the app or extension.", + "type": "function", + "parameters": [] + }, + { + "name": "requestUpdateCheck", + "type": "function", + "description": "Requests an update check for this app/extension.", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "status", + "type": "string", + "enum": ["throttled", "no_update", "update_available"], + "description": "Result of the update check." + }, + { + "name": "details", + "type": "object", + "optional": true, + "properties": { + "version": { + "type": "string", + "description": "The version of the available update." + } + }, + "description": "If an update is available, this contains more information about the available update." + } + ] + } + ] + }, + { + "name": "restart", + "description": "Restart the ChromeOS device when the app runs in kiosk mode. Otherwise, it's no-op.", + "type": "function", + "parameters": [] + }, + { + "name": "connect", + "type": "function", + "nocompile": true, + "description": "Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and <a href=\"manifest/externally_connectable.html\">web messaging</a>. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).", + "parameters": [ + {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for <a href=\"manifest/externally_connectable.html\">web messaging</a>."}, + { + "type": "object", + "name": "connectInfo", + "properties": { + "name": { "type": "string", "optional": true, "description": "Will be passed into onConnect for processes that are listening for the connection event." }, + "includeTlsChannelId": { "type": "boolean", "optional": true, "description": "Whether the TLS channel ID will be passed into onConnectExternal for processes that are listening for the connection event." } + }, + "optional": true + } + ], + "returns": { + "$ref": "Port", + "description": "Port through which messages can be sent and received. The port's $(ref:runtime.Port onDisconnect) event is fired if the extension/app does not exist. " + } + }, + { + "name": "connectNative", + "type": "function", + "nocompile": true, + "description": "Connects to a native application in the host machine.", + "parameters": [ + { + "type": "string", + "name": "application", + "description": "The name of the registered application to connect to." + } + ], + "returns": { + "$ref": "Port", + "description": "Port through which messages can be sent and received with the application" + } + }, + { + "name": "sendMessage", + "type": "function", + "nocompile": true, + "allowAmbiguousOptionalArguments": true, + "description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).", + "parameters": [ + {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension/app to send the message to. If omitted, the message will be sent to your own extension/app. Required if sending messages from a web page for <a href=\"manifest/externally_connectable.html\">web messaging</a>."}, + { "type": "any", "name": "message" }, + { + "type": "object", + "name": "options", + "properties": { + "includeTlsChannelId": { "type": "boolean", "optional": true, "description": "Whether the TLS channel ID will be passed into onMessageExternal for processes that are listening for the connection event." } + }, + "optional": true + }, + { + "type": "function", + "name": "responseCallback", + "optional": true, + "parameters": [ + { + "name": "response", + "type": "any", + "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the extension, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message." + } + ] + } + ] + }, + { + "name": "sendNativeMessage", + "type": "function", + "nocompile": true, + "description": "Send a single message to a native application.", + "parameters": [ + { + "name": "application", + "description": "The name of the native messaging host.", + "type": "string" + }, + { + "name": "message", + "description": "The message that will be passed to the native messaging host.", + "type": "object", + "additionalProperties": { + "type": "any" + } + }, + { + "type": "function", + "name": "responseCallback", + "optional": true, + "parameters": [ + { + "name": "response", + "type": "any", + "description": "The response message sent by the native messaging host. If an error occurs while connecting to the native messaging host, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message.", + "additionalProperties": { + "type": "any" + } + } + ] + } + ] + }, + { + "name": "getPlatformInfo", + "type": "function", + "description": "Returns information about the current platform.", + "parameters": [ + { + "type": "function", + "name": "callback", + "description": "Called with results", + "parameters": [ + { + "name": "platformInfo", + "$ref": "PlatformInfo" + } + ] + } + ] + }, + { + "name": "getPackageDirectoryEntry", + "type": "function", + "description": "Returns a DirectoryEntry for the package directory.", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "directoryEntry", + "type": "object", + "additionalProperties": { "type": "any" }, + "isInstanceOf": "DirectoryEntry" + } + ] + } + ] + } + ], + "events": [ + { + "name": "onStartup", + "type": "function", + "description": "Fired when a profile that has this extension installed first starts up. This event is not fired when an incognito profile is started, even if this extension is operating in 'split' incognito mode." + }, + { + "name": "onInstalled", + "type": "function", + "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when Chrome is updated to a new version.", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "reason": { + "type": "string", + "enum": ["install", "update", "chrome_update"], + "description": "The reason that this event is being dispatched." + }, + "previousVersion": { + "type": "string", + "optional": true, + "description": "Indicates the previous version of the extension, which has just been updated. This is present only if 'reason' is 'update'." + } + } + } + ] + }, + { + "name": "onSuspend", + "type": "function", + "description": "Sent to the event page just before it is unloaded. This gives the extension opportunity to do some clean up. Note that since the page is unloading, any asynchronous operations started while handling this event are not guaranteed to complete. If more activity for the event page occurs before it gets unloaded the onSuspendCanceled event will be sent and the page won't be unloaded. " + }, + { + "name": "onSuspendCanceled", + "type": "function", + "description": "Sent after onSuspend to indicate that the app won't be unloaded after all." + }, + { + "name": "onUpdateAvailable", + "type": "function", + "description": "Fired when an update is available, but isn't installed immediately because the app is currently running. If you do nothing, the update will be installed the next time the background page gets unloaded, if you want it to be installed sooner you can explicitly call chrome.runtime.reload().", + "parameters": [ + { + "type": "object", + "name": "details", + "properties": { + "version": { + "type": "string", + "description": "The version number of the available update." + } + }, + "additionalProperties": { "type": "any" }, + "description": "The manifest details of the available update." + } + ] + }, + { + // TODO(xiyuan): onBrowserUpdateAvailable is deprecated in favor of + // onRestartRequired. We should remove it when we are sure it is unused. + "name": "onBrowserUpdateAvailable", + "type": "function", + "description": "Fired when a Chrome update is available, but isn't installed immediately because a browser restart is required.", + "deprecated": "Please use $(ref:runtime.onRestartRequired).", + "parameters": [] + }, + { + "name": "onConnect", + "type": "function", + "nocompile": true, + "options": { + "unmanaged": true + }, + "description": "Fired when a connection is made from either an extension process or a content script.", + "parameters": [ + {"$ref": "Port", "name": "port"} + ] + }, + { + "name": "onConnectExternal", + "type": "function", + "nocompile": true, + "options": { + "unmanaged": true + }, + "description": "Fired when a connection is made from another extension.", + "parameters": [ + {"$ref": "Port", "name": "port"} + ] + }, + { + "name": "onMessage", + "type": "function", + "options": { + "unmanaged": true + }, + "nocompile": true, + "description": "Fired when a message is sent from either an extension process or a content script.", + "parameters": [ + {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."}, + {"name": "sender", "$ref": "MessageSender" }, + {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." } + ], + "returns": { + "type": "boolean", + "optional": true, + "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns." + } + }, + { + "name": "onMessageExternal", + "type": "function", + "options": { + "unmanaged": true + }, + "nocompile": true, + "description": "Fired when a message is sent from another extension/app. Cannot be used in a content script.", + "parameters": [ + {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."}, + {"name": "sender", "$ref": "MessageSender" }, + {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." } + ], + "returns": { + "type": "boolean", + "optional": true, + "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns." + } + }, + { + "name": "onRestartRequired", + "type": "function", + "description": "Fired when an app or the device that it runs on needs to be restarted. The app should close all its windows at its earliest convenient time to let the restart to happen. If the app does nothing, a restart will be enforced after a 24-hour grace period has passed. Currently, this event is only fired for Chrome OS kiosk apps.", + "parameters": [ + { + "type": "string", + "name": "reason", + "description": "The reason that the event is being dispatched. 'app_update' is used when the restart is needed because the application is updated to a newer version. 'os_update' is used when the restart is needed because the browser/OS is updated to a newer version. 'periodic' is used when the system runs for more than the permitted uptime set in the enterprise policy.", + "enum": ["app_update", "os_update", "periodic"] + } + ] + } + ] + } +] diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index 2ec0dcd..f2774b2 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -255,6 +255,10 @@ 'browser/api/dns/host_resolver_wrapper.h', 'browser/api/extensions_api_client.cc', 'browser/api/extensions_api_client.h', + 'browser/api/runtime/runtime_api.cc', + 'browser/api/runtime/runtime_api.h', + 'browser/api/runtime/runtime_api_delegate.cc', + 'browser/api/runtime/runtime_api_delegate.h', 'browser/api/socket/socket.cc', 'browser/api/socket/socket.h', 'browser/api/socket/socket_api.cc', @@ -407,6 +411,8 @@ # when enable_extensions==0. 'sources/': [ ['exclude', '^browser/api/'], + ['include', '^browser/api/runtime/runtime_api.cc'], + ['include', '^browser/api/runtime/runtime_api_delegate.cc'], ], 'sources!': [ 'browser/browser_context_keyed_service_factories.cc', @@ -549,17 +555,21 @@ 'dependencies': [ '../base/base.gyp:base', '../testing/gtest.gyp:gtest', + 'common/api/api.gyp:extensions_api', 'extensions_browser', 'extensions_common', ], 'include_dirs': [ '..', + '<(SHARED_INTERMEDIATE_DIR)', ], 'sources': [ 'browser/test_extensions_browser_client.cc', 'browser/test_extensions_browser_client.h', 'browser/test_management_policy.cc', 'browser/test_management_policy.h', + 'browser/test_runtime_api_delegate.cc', + 'browser/test_runtime_api_delegate.h', 'common/extension_builder.cc', 'common/extension_builder.h', 'common/test_util.cc', |