summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorelijahtaylor@chromium.org <elijahtaylor@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-30 01:04:35 +0000
committerelijahtaylor@chromium.org <elijahtaylor@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-30 01:04:35 +0000
commitb56d3c7fc3e61736747d63aac703bab4a51df8db (patch)
tree16bc7b6de6144cf8b633c7b3f5d0982fc1c79b1e
parentaa249b505342a9e1203440dcd04b94901fbf8f80 (diff)
downloadchromium_src-b56d3c7fc3e61736747d63aac703bab4a51df8db.zip
chromium_src-b56d3c7fc3e61736747d63aac703bab4a51df8db.tar.gz
chromium_src-b56d3c7fc3e61736747d63aac703bab4a51df8db.tar.bz2
Basic multi-module support
Allows resource export from one extension that can be imported into another extension's namespace. BUG=236044 TEST=browser_tests ExtensionApiTest.SharedModule TEST=unit_tests SharedModuleManifestTest.* Review URL: https://chromiumcodereview.appspot.com/13971005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@197201 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/generated_resources.grd6
-rw-r--r--chrome/browser/extensions/admin_policy.cc1
-rw-r--r--chrome/browser/extensions/chrome_manifest_parser.cc2
-rw-r--r--chrome/browser/extensions/crx_installer.cc36
-rw-r--r--chrome/browser/extensions/crx_installer.h2
-rw-r--r--chrome/browser/extensions/extension_protocols.cc54
-rw-r--r--chrome/browser/extensions/shared_module_apitest.cc26
-rw-r--r--chrome/browser/renderer_host/chrome_render_view_host_observer.cc1
-rw-r--r--chrome/chrome_common.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/extensions/api/_manifest_features.json44
-rw-r--r--chrome/common/extensions/extension_constants.cc3
-rw-r--r--chrome/common/extensions/extension_constants.h3
-rw-r--r--chrome/common/extensions/extension_manifest_constants.cc20
-rw-r--r--chrome/common/extensions/extension_manifest_constants.h12
-rw-r--r--chrome/common/extensions/features/base_feature_provider_unittest.cc4
-rw-r--r--chrome/common/extensions/features/simple_feature.cc3
-rw-r--r--chrome/common/extensions/features/simple_feature_unittest.cc4
-rw-r--r--chrome/common/extensions/manifest.cc2
-rw-r--r--chrome/common/extensions/manifest.h4
-rw-r--r--chrome/common/extensions/manifest_handlers/shared_module_info.cc229
-rw-r--r--chrome/common/extensions/manifest_handlers/shared_module_info.h71
-rw-r--r--chrome/common/extensions/manifest_handlers/shared_module_manifest_unittest.cc117
-rw-r--r--chrome/common/extensions/manifest_unittest.cc9
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/import_non_existent/manifest.json9
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/import_pass/check.js11
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/import_pass/main.html5
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/import_pass/manifest.json14
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/import_wrong_version/manifest.json10
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/shared/fail.js5
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/shared/manifest.json11
-rw-r--r--chrome/test/data/extensions/api_test/shared_module/shared/pass.js5
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_export.json11
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_export_and_import.json14
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_export_foo.json11
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_export_not_dict.json9
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_export_resource_not_string.json12
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_export_resources_not_list.json9
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_import.json14
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_id.json10
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_version.json11
-rw-r--r--chrome/test/data/extensions/manifest_tests/shared_module_import_not_list.json8
43 files changed, 809 insertions, 27 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 8bd2af8..c91c002 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5257,6 +5257,12 @@ Make sure you do not expose any sensitive information.
<message name="IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION" desc="Error displayed during installation of a side-loaded app, extension, or theme when the version of the referenced extension does not match the version the developer declared during registration.">
Expected version "<ph name="EXPECTED_VERSION">$1<ex>2.0</ex></ph>", but version was "<ph name="NEW_ID">$2<ex>1.0</ex></ph>".
</message>
+ <message name="IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_FOUND" desc="Error displayed during installation of an extension when an import dependency is not found.">
+ Required extension with ID "<ph name="IMPORT_ID">$1<ex>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</ex></ph>" and minimum version "<ph name="IMPORT_VERSION">$2<ex>1.0</ex></ph>" not found.
+ </message>
+ <message name="IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_SHARED_MODULE" desc="Error displayed during installation of an extension which tries to imports resources from an extension which is not a shared module.">
+ Unable to import extension with ID "<ph name="IMPORT_ID">$1<ex>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</ex></ph>" because it is not a shared module.
+ </message>
<message name="IDS_EXTENSION_OVERLAPPING_WEB_EXTENT" desc="Error message when a user tries to install an app with a web extent that overlaps another installed app.">
Could not add application because it conflicts with "<ph name="APP_NAME">$1<ex>Google Mail</ex></ph>".
</message>
diff --git a/chrome/browser/extensions/admin_policy.cc b/chrome/browser/extensions/admin_policy.cc
index 2cdbcd5..6500106 100644
--- a/chrome/browser/extensions/admin_policy.cc
+++ b/chrome/browser/extensions/admin_policy.cc
@@ -80,6 +80,7 @@ bool UserMayLoad(const base::ListValue* blacklist,
case Manifest::TYPE_HOSTED_APP:
case Manifest::TYPE_LEGACY_PACKAGED_APP:
case Manifest::TYPE_PLATFORM_APP:
+ case Manifest::TYPE_SHARED_MODULE:
base::FundamentalValue type_value(extension->GetType());
if (allowed_types &&
allowed_types->Find(type_value) == allowed_types->end())
diff --git a/chrome/browser/extensions/chrome_manifest_parser.cc b/chrome/browser/extensions/chrome_manifest_parser.cc
index 233c25a..9f89bbf 100644
--- a/chrome/browser/extensions/chrome_manifest_parser.cc
+++ b/chrome/browser/extensions/chrome_manifest_parser.cc
@@ -13,6 +13,7 @@
#include "chrome/common/extensions/manifest_handlers/kiosk_enabled_info.h"
#include "chrome/common/extensions/manifest_handlers/offline_enabled_info.h"
#include "chrome/common/extensions/manifest_handlers/requirements_handler.h"
+#include "chrome/common/extensions/manifest_handlers/shared_module_info.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
@@ -29,6 +30,7 @@ ChromeManifestParser::ChromeManifestParser(Profile* profile)
(new OfflineEnabledHandler)->Register();
(new OptionsPageHandler)->Register();
(new RequirementsHandler)->Register();
+ (new SharedModuleHandler)->Register();
(new UpdateURLHandler)->Register();
(new URLOverridesHandler)->Register();
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index f0a9365..2efea97 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -42,6 +42,7 @@
#include "chrome/common/extensions/feature_switch.h"
#include "chrome/common/extensions/manifest.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
+#include "chrome/common/extensions/manifest_handlers/shared_module_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/resource_dispatcher_host.h"
@@ -60,6 +61,7 @@
using content::BrowserThread;
using content::UserMetricsAction;
+using extensions::SharedModuleInfo;
namespace extensions {
@@ -400,14 +402,42 @@ void CrxInstaller::OnUnpackSuccess(const base::FilePath& temp_dir,
if (!BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
- base::Bind(&CrxInstaller::CheckRequirements, this)))
+ base::Bind(&CrxInstaller::CheckImportsAndRequirements, this)))
NOTREACHED();
}
-void CrxInstaller::CheckRequirements() {
+void CrxInstaller::CheckImportsAndRequirements() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!frontend_weak_.get() || frontend_weak_->browser_terminating())
return;
+
+ if (SharedModuleInfo::ImportsModules(extension_)) {
+ const std::vector<SharedModuleInfo::ImportInfo>& imports =
+ SharedModuleInfo::GetImports(extension_);
+ std::vector<SharedModuleInfo::ImportInfo>::const_iterator i;
+ for (i = imports.begin(); i != imports.end(); ++i) {
+ Version version_required(i->minimum_version);
+ const Extension* imported_module =
+ frontend_weak_->GetExtensionById(i->extension_id, true);
+ if (!imported_module ||
+ (version_required.IsValid() &&
+ imported_module->version()->CompareTo(version_required) < 0)) {
+ ReportFailureFromUIThread(
+ CrxInstallerError(l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_FOUND,
+ ASCIIToUTF16(i->extension_id),
+ ASCIIToUTF16(i->minimum_version))));
+ return;
+ }
+ if (!SharedModuleInfo::IsSharedModule(imported_module)) {
+ ReportFailureFromUIThread(
+ CrxInstallerError(l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_INSTALL_DEPENDENCY_NOT_SHARED_MODULE,
+ ASCIIToUTF16(i->extension_id))));
+ return;
+ }
+ }
+ }
AddRef(); // Balanced in OnRequirementsChecked().
requirements_checker_->Check(extension_,
base::Bind(&CrxInstaller::OnRequirementsChecked,
@@ -417,7 +447,7 @@ void CrxInstaller::CheckRequirements() {
void CrxInstaller::OnRequirementsChecked(
std::vector<std::string> requirement_errors) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- Release(); // Balanced in CheckRequirements().
+ Release(); // Balanced in CheckImportsAndRequirements().
if (!requirement_errors.empty()) {
if (error_on_unsupported_requirements_) {
ReportFailureFromUIThread(CrxInstallerError(
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index 1913591..e394c33 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -222,7 +222,7 @@ class CrxInstaller
const Extension* extension) OVERRIDE;
// Called on the UI thread to start the requirements check on the extension.
- void CheckRequirements();
+ void CheckImportsAndRequirements();
// Runs on the UI thread. Callback from RequirementsChecker.
void OnRequirementsChecked(std::vector<std::string> requirement_errors);
diff --git a/chrome/browser/extensions/extension_protocols.cc b/chrome/browser/extensions/extension_protocols.cc
index b0da9b0..a6ed685 100644
--- a/chrome/browser/extensions/extension_protocols.cc
+++ b/chrome/browser/extensions/extension_protocols.cc
@@ -16,6 +16,7 @@
#include "base/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/worker_pool.h"
+#include "base/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_info_map.h"
#include "chrome/browser/extensions/image_loader.h"
@@ -26,6 +27,7 @@
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/incognito_handler.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
+#include "chrome/common/extensions/manifest_handlers/shared_module_info.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "chrome/common/extensions/web_accessible_resources_handler.h"
#include "chrome/common/url_constants.h"
@@ -45,6 +47,7 @@
using content::ResourceRequestInfo;
using extensions::Extension;
+using extensions::SharedModuleInfo;
namespace {
@@ -205,14 +208,13 @@ class URLRequestExtensionJob : public net::URLRequestFileJob {
net::NetworkDelegate* network_delegate,
const std::string& extension_id,
const base::FilePath& directory_path,
+ const base::FilePath& relative_path,
const std::string& content_security_policy,
bool send_cors_header)
: net::URLRequestFileJob(request, network_delegate, base::FilePath()),
// TODO(tc): Move all of these files into resources.pak so we don't break
// when updating on Linux.
- resource_(extension_id, directory_path,
- extension_file_util::ExtensionURLToRelativeFilePath(
- request->url())),
+ resource_(extension_id, directory_path, relative_path),
weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
response_info_.headers = BuildHttpHeaders(content_security_policy,
send_cors_header);
@@ -388,7 +390,7 @@ net::URLRequestJob*
ExtensionProtocolHandler::MaybeCreateJob(
net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
// chrome-extension://extension-id/resource/path.js
- const std::string& extension_id = request->url().host();
+ std::string extension_id = request->url().host();
const Extension* extension =
extension_info_map_->extensions().GetByID(extension_id);
@@ -462,10 +464,54 @@ ExtensionProtocolHandler::MaybeCreateJob(
}
}
+ relative_path =
+ extension_file_util::ExtensionURLToRelativeFilePath(request->url());
+
+ if (SharedModuleInfo::IsImportedPath(path)) {
+ std::string new_extension_id;
+ std::string new_relative_path;
+ SharedModuleInfo::ParseImportedPath(path, &new_extension_id,
+ &new_relative_path);
+ const Extension* new_extension =
+ extension_info_map_->extensions().GetByID(new_extension_id);
+
+ bool first_party_in_import = false;
+ // NB: This first_party_for_cookies call is not for security, it is only
+ // used so an exported extension can limit the visible surface to the
+ // extension that imports it, more or less constituting its API.
+ const std::string& first_party_path =
+ request->first_party_for_cookies().path();
+ if (SharedModuleInfo::IsImportedPath(first_party_path)) {
+ std::string first_party_id;
+ std::string dummy;
+ SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id,
+ &dummy);
+ if (first_party_id == new_extension_id) {
+ first_party_in_import = true;
+ }
+ }
+
+ if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) &&
+ new_extension &&
+ (first_party_in_import ||
+ SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) {
+ directory_path = new_extension->path();
+ extension_id = new_extension_id;
+#if defined(OS_POSIX)
+ relative_path = base::FilePath(new_relative_path);
+#elif defined(OS_WIN)
+ relative_path = base::FilePath(UTF8ToWide(new_relative_path));
+#endif
+ } else {
+ return NULL;
+ }
+ }
+
return new URLRequestExtensionJob(request,
network_delegate,
extension_id,
directory_path,
+ relative_path,
content_security_policy,
send_cors_header);
}
diff --git a/chrome/browser/extensions/shared_module_apitest.cc b/chrome/browser/extensions/shared_module_apitest.cc
new file mode 100644
index 0000000..e10a467
--- /dev/null
+++ b/chrome/browser/extensions/shared_module_apitest.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 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/extensions/extension_apitest.h"
+
+using extensions::Extension;
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, SharedModule) {
+ // import_pass depends on this shared module.
+ // NB: We use LoadExtension instead of InstallExtension here so the public-key
+ // in 'shared' is used to generate the extension ID so it can be imported
+ // correctly. We use InstallExtension otherwise so the loads happen through
+ // the CRX installer which validates imports.
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("shared_module").AppendASCII("shared")));
+
+ EXPECT_TRUE(RunExtensionTest("shared_module/import_pass"));
+
+ EXPECT_FALSE(InstallExtension(
+ test_data_dir_.AppendASCII("shared_module")
+ .AppendASCII("import_wrong_version"), 0));
+ EXPECT_FALSE(InstallExtension(
+ test_data_dir_.AppendASCII("shared_module")
+ .AppendASCII("import_non_existent"), 0));
+}
diff --git a/chrome/browser/renderer_host/chrome_render_view_host_observer.cc b/chrome/browser/renderer_host/chrome_render_view_host_observer.cc
index 199d8d4..bcac447 100644
--- a/chrome/browser/renderer_host/chrome_render_view_host_observer.cc
+++ b/chrome/browser/renderer_host/chrome_render_view_host_observer.cc
@@ -127,6 +127,7 @@ void ChromeRenderViewHostObserver::InitRenderViewForExtensions() {
case Manifest::TYPE_UNKNOWN:
case Manifest::TYPE_THEME:
+ case Manifest::TYPE_SHARED_MODULE:
break;
}
}
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 0360a90..96a2dc4 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -265,6 +265,8 @@
'common/extensions/manifest_handlers/requirements_handler.h',
'common/extensions/manifest_handlers/sandboxed_page_info.cc',
'common/extensions/manifest_handlers/sandboxed_page_info.h',
+ 'common/extensions/manifest_handlers/shared_module_info.cc',
+ 'common/extensions/manifest_handlers/shared_module_info.h',
'common/extensions/manifest_handlers/theme_handler.cc',
'common/extensions/manifest_handlers/theme_handler.h',
'common/extensions/manifest_url_handler.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 2ee6816..504d03a 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1392,6 +1392,7 @@
'browser/extensions/process_management_browsertest.cc',
'browser/extensions/requirements_checker_browsertest.cc',
'browser/extensions/sandboxed_pages_apitest.cc',
+ 'browser/extensions/shared_module_apitest.cc',
'browser/extensions/startup_helper_browsertest.cc',
'browser/extensions/stubs_apitest.cc',
'browser/extensions/subscribe_page_action_browsertest.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 6bb1a79..2e8f54c 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1565,6 +1565,7 @@
'common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc',
'common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc',
'common/extensions/manifest_handlers/externally_connectable_unittest.cc',
+ 'common/extensions/manifest_handlers/shared_module_manifest_unittest.cc',
'common/extensions/manifest_tests/extension_manifest_test.cc',
'common/extensions/manifest_tests/extension_manifests_background_unittest.cc',
'common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc',
diff --git a/chrome/common/extensions/api/_manifest_features.json b/chrome/common/extensions/api/_manifest_features.json
index 48e580d..11fb708 100644
--- a/chrome/common/extensions/api/_manifest_features.json
+++ b/chrome/common/extensions/api/_manifest_features.json
@@ -29,16 +29,16 @@
"extension_types": ["platform_app"],
"min_manifest_version": 2
},
- "app.launch": {
- "channel": "stable",
- "extension_types": ["packaged_app", "hosted_app"]
- },
"app.isolation": {
"channel": "stable",
// Platform apps always have isolated storage, thus they cannot specify it
// via the manifest.
"extension_types": ["packaged_app", "hosted_app"]
},
+ "app.launch": {
+ "channel": "stable",
+ "extension_types": ["packaged_app", "hosted_app"]
+ },
"author": {
"channel": "stable",
"extension_types": "all"
@@ -143,6 +143,14 @@
]
}
],
+ "export": {
+ "channel": "dev",
+ "extension_types": ["shared_module"],
+ "whitelist": [
+ "gpcckkmippodnppallflahfabmeilgjg", // browser and unit tests
+ "6EAED1924DB611B6EEF2A664BD077BE7EAD33B8F"
+ ]
+ },
"externally_connectable": {
"channel": "stable",
"extension_types": [
@@ -153,10 +161,6 @@
"channel": "stable",
"extension_types": ["extension", "packaged_app"]
},
- "media_galleries_handlers": {
- "channel": "stable",
- "extension_types": ["packaged_app", "platform_app"]
- },
"file_handlers": {
"channel": "stable",
"extension_types": ["platform_app"]
@@ -169,6 +173,10 @@
"channel": "stable",
"extension_types": "all"
},
+ "import": {
+ "channel": "dev",
+ "extension_types": "all"
+ },
"incognito": {
"channel": "stable",
"extension_types": ["extension", "packaged_app"]
@@ -187,10 +195,20 @@
"channel": "stable",
"extension_types": "all"
},
+ "kiosk_enabled": {
+ "channel": "stable",
+ "extension_types": [
+ "platform_app"
+ ]
+ },
"manifest_version": {
"channel": "stable",
"extension_types": "all"
},
+ "media_galleries_handlers": {
+ "channel": "stable",
+ "extension_types": ["packaged_app", "platform_app"]
+ },
"mime_types": {
"channel": "stable",
"extension_types": [ "extension", "packaged_app", "platform_app" ],
@@ -218,10 +236,10 @@
"channel": "stable",
"extension_types": "all"
},
- "kiosk_enabled": {
+ "oauth2": {
"channel": "stable",
"extension_types": [
- "platform_app"
+ "extension", "packaged_app", "platform_app"
]
},
"offline_enabled": {
@@ -246,12 +264,6 @@
"extension", "packaged_app", "hosted_app"
]
},
- "oauth2": {
- "channel": "stable",
- "extension_types": [
- "extension", "packaged_app", "platform_app"
- ]
- },
"page_action": {
"channel": "stable",
"extension_types": ["extension"]
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index dd0d07e..c6e8847 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -102,6 +102,9 @@ const char kDecodedMessageCatalogsFilename[] = "DECODED_MESSAGE_CATALOGS";
const char kGeneratedBackgroundPageFilename[] =
"_generated_background_page.html";
+
+const char kModulesDir[] = "_modules";
+
}
// These must match the values expected by the chrome.management extension API.
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 27b4287..8ce965f 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -64,6 +64,9 @@ namespace extension_filenames {
// The filename to use for a background page generated from
// background.scripts.
extern const char kGeneratedBackgroundPageFilename[];
+
+ // Path to imported modules.
+ extern const char kModulesDir[];
}
// Keys in the dictionary returned by Extension::GetBasicInfo().
diff --git a/chrome/common/extensions/extension_manifest_constants.cc b/chrome/common/extensions/extension_manifest_constants.cc
index 72b9d97..7dc68d5 100644
--- a/chrome/common/extensions/extension_manifest_constants.cc
+++ b/chrome/common/extensions/extension_manifest_constants.cc
@@ -34,6 +34,7 @@ const char kDisplayInNewTabPage[] = "display_in_new_tab_page";
const char kEventName[] = "event_name";
const char kExcludeGlobs[] = "exclude_globs";
const char kExcludeMatches[] = "exclude_matches";
+const char kExport[] = "export";
const char kExternallyConnectable[] = "externally_connectable";
const char kFileAccessList[] = "file_access";
const char kFileFilters[] = "file_filters";
@@ -45,6 +46,7 @@ const char kFileHandlerTypes[] = "types";
const char kHomepageURL[] = "homepage_url";
const char kIcons[] = "icons";
const char kId[] = "id";
+const char kImport[] = "import";
const char kIncognito[] = "incognito";
const char kIncludeGlobs[] = "include_globs";
const char kInputComponents[] = "input_components";
@@ -74,6 +76,7 @@ const char kLayouts[] = "layouts";
const char kManifestVersion[] = "manifest_version";
const char kMatches[] = "matches";
const char kMinimumChromeVersion[] = "minimum_chrome_version";
+const char kMinimumVersion[] = "minimum_version";
const char kMIMETypes[] = "mime_types";
const char kMimeTypesHandler[] = "mime_types_handler";
const char kName[] = "name";
@@ -107,6 +110,7 @@ const char kPlugins[] = "plugins";
const char kPluginsPath[] = "path";
const char kPluginsPublic[] = "public";
const char kPublicKey[] = "key";
+const char kResources[] = "resources";
const char kRequirements[] = "requirements";
const char kRunAt[] = "run_at";
const char kSandboxedPages[] = "sandbox.pages";
@@ -263,6 +267,14 @@ const char kInvalidExcludeMatch[] =
"Invalid value for 'content_scripts[*].exclude_matches[*]': *";
const char kInvalidExcludeMatches[] =
"Invalid value for 'content_scripts[*].exclude_matches'.";
+const char kInvalidExport[] =
+ "Invalid value for 'export'.";
+const char kInvalidExportPermissions[] =
+ "Permissions are not allowed for extensions that export resources.";
+const char kInvalidExportResources[] =
+ "Invalid value for 'export.resources'.";
+const char kInvalidExportResourcesString[] =
+ "Invalid value for 'export.resources[*]'.";
const char kInvalidFileAccessList[] =
"Invalid value for 'file_access'.";
const char kInvalidFileAccessValue[] =
@@ -293,6 +305,14 @@ const char kInvalidIconPath[] =
"Invalid value for 'icons[\"*\"]'.";
const char kInvalidIcons[] =
"Invalid value for 'icons'.";
+const char kInvalidImport[] =
+ "Invalid value for 'import'.";
+const char kInvalidImportAndExport[] =
+ "Simultaneous 'import' and 'export' are not allowed.";
+const char kInvalidImportId[] =
+ "Invalid value for 'import[*].id'.";
+const char kInvalidImportVersion[] =
+ "Invalid value for 'import[*].minimum_version'.";
const char kInvalidIncognitoBehavior[] =
"Invalid value for 'incognito'.";
const char kInvalidIncognitoModeForPlatformApp[] =
diff --git a/chrome/common/extensions/extension_manifest_constants.h b/chrome/common/extensions/extension_manifest_constants.h
index ef3c591..6270e9b 100644
--- a/chrome/common/extensions/extension_manifest_constants.h
+++ b/chrome/common/extensions/extension_manifest_constants.h
@@ -40,6 +40,7 @@ namespace extension_manifest_keys {
extern const char kEventName[];
extern const char kExcludeGlobs[];
extern const char kExcludeMatches[];
+ extern const char kExport[];
extern const char kExternallyConnectable[];
extern const char kFileAccessList[];
extern const char kFileHandlers[];
@@ -51,6 +52,7 @@ namespace extension_manifest_keys {
extern const char kHomepageURL[];
extern const char kIcons[];
extern const char kId[];
+ extern const char kImport[];
extern const char kIncognito[];
extern const char kIncludeGlobs[];
extern const char kInputComponents[];
@@ -82,6 +84,7 @@ namespace extension_manifest_keys {
extern const char kMIMETypes[];
extern const char kMimeTypesHandler[];
extern const char kMinimumChromeVersion[];
+ extern const char kMinimumVersion[];
extern const char kNaClModules[];
extern const char kNaClModulesMIMEType[];
extern const char kNaClModulesPath[];
@@ -113,6 +116,7 @@ namespace extension_manifest_keys {
extern const char kPluginsPath[];
extern const char kPluginsPublic[];
extern const char kPublicKey[];
+ extern const char kResources[];
extern const char kRequirements[];
extern const char kRunAt[];
extern const char kSandboxedPages[];
@@ -225,6 +229,10 @@ namespace extension_manifest_errors {
extern const char kInvalidDisplayInNewTabPage[];
extern const char kInvalidExcludeMatch[];
extern const char kInvalidExcludeMatches[];
+ extern const char kInvalidExport[];
+ extern const char kInvalidExportPermissions[];
+ extern const char kInvalidExportResources[];
+ extern const char kInvalidExportResourcesString[];
extern const char kInvalidFileAccessList[];
extern const char kInvalidFileAccessValue[];
extern const char kInvalidFileBrowserHandler[];
@@ -241,6 +249,10 @@ namespace extension_manifest_errors {
extern const char kInvalidHomepageURL[];
extern const char kInvalidIconPath[];
extern const char kInvalidIcons[];
+ extern const char kInvalidImport[];
+ extern const char kInvalidImportAndExport[];
+ extern const char kInvalidImportId[];
+ extern const char kInvalidImportVersion[];
extern const char kInvalidIncognitoBehavior[];
extern const char kInvalidIncognitoModeForPlatformApp[];
extern const char kInvalidInputComponents[];
diff --git a/chrome/common/extensions/features/base_feature_provider_unittest.cc b/chrome/common/extensions/features/base_feature_provider_unittest.cc
index 1670f9f..a1de8b5 100644
--- a/chrome/common/extensions/features/base_feature_provider_unittest.cc
+++ b/chrome/common/extensions/features/base_feature_provider_unittest.cc
@@ -21,7 +21,7 @@ TEST_F(BaseFeatureProviderTest, ManifestFeatures) {
SimpleFeature* feature =
static_cast<SimpleFeature*>(provider->GetFeature("description"));
ASSERT_TRUE(feature);
- EXPECT_EQ(5u, feature->extension_types()->size());
+ EXPECT_EQ(6u, feature->extension_types()->size());
EXPECT_EQ(1u, feature->extension_types()->count(Manifest::TYPE_EXTENSION));
EXPECT_EQ(1u,
feature->extension_types()->count(Manifest::TYPE_LEGACY_PACKAGED_APP));
@@ -29,6 +29,8 @@ TEST_F(BaseFeatureProviderTest, ManifestFeatures) {
feature->extension_types()->count(Manifest::TYPE_PLATFORM_APP));
EXPECT_EQ(1u, feature->extension_types()->count(Manifest::TYPE_HOSTED_APP));
EXPECT_EQ(1u, feature->extension_types()->count(Manifest::TYPE_THEME));
+ EXPECT_EQ(1u,
+ feature->extension_types()->count(Manifest::TYPE_SHARED_MODULE));
base::DictionaryValue manifest;
manifest.SetString("name", "test extension");
diff --git a/chrome/common/extensions/features/simple_feature.cc b/chrome/common/extensions/features/simple_feature.cc
index 20d91f15..23481f0 100644
--- a/chrome/common/extensions/features/simple_feature.cc
+++ b/chrome/common/extensions/features/simple_feature.cc
@@ -28,6 +28,7 @@ struct Mappings {
extension_types["packaged_app"] = Manifest::TYPE_LEGACY_PACKAGED_APP;
extension_types["hosted_app"] = Manifest::TYPE_HOSTED_APP;
extension_types["platform_app"] = Manifest::TYPE_PLATFORM_APP;
+ extension_types["shared_module"] = Manifest::TYPE_SHARED_MODULE;
contexts["blessed_extension"] = Feature::BLESSED_EXTENSION_CONTEXT;
contexts["unblessed_extension"] = Feature::UNBLESSED_EXTENSION_CONTEXT;
@@ -165,6 +166,8 @@ std::string GetDisplayTypeName(Manifest::Type type) {
return "theme";
case Manifest::TYPE_USER_SCRIPT:
return "user script";
+ case Manifest::TYPE_SHARED_MODULE:
+ return "shared module";
}
NOTREACHED();
diff --git a/chrome/common/extensions/features/simple_feature_unittest.cc b/chrome/common/extensions/features/simple_feature_unittest.cc
index 4f92a34..2fd6d21 100644
--- a/chrome/common/extensions/features/simple_feature_unittest.cc
+++ b/chrome/common/extensions/features/simple_feature_unittest.cc
@@ -377,16 +377,18 @@ TEST_F(ExtensionSimpleFeatureTest, ParsePackageTypes) {
extension_types->Append(new base::StringValue("packaged_app"));
extension_types->Append(new base::StringValue("hosted_app"));
extension_types->Append(new base::StringValue("platform_app"));
+ extension_types->Append(new base::StringValue("shared_module"));
value->Set("extension_types", extension_types);
scoped_ptr<SimpleFeature> feature(new SimpleFeature());
feature->Parse(value.get());
- EXPECT_EQ(5u, feature->extension_types()->size());
+ EXPECT_EQ(6u, feature->extension_types()->size());
EXPECT_TRUE(feature->extension_types()->count(Manifest::TYPE_EXTENSION));
EXPECT_TRUE(feature->extension_types()->count(Manifest::TYPE_THEME));
EXPECT_TRUE(feature->extension_types()->count(
Manifest::TYPE_LEGACY_PACKAGED_APP));
EXPECT_TRUE(feature->extension_types()->count(Manifest::TYPE_HOSTED_APP));
EXPECT_TRUE(feature->extension_types()->count(Manifest::TYPE_PLATFORM_APP));
+ EXPECT_TRUE(feature->extension_types()->count(Manifest::TYPE_SHARED_MODULE));
value->SetString("extension_types", "all");
scoped_ptr<SimpleFeature> feature2(new SimpleFeature());
diff --git a/chrome/common/extensions/manifest.cc b/chrome/common/extensions/manifest.cc
index 78ae189..b57e16d 100644
--- a/chrome/common/extensions/manifest.cc
+++ b/chrome/common/extensions/manifest.cc
@@ -106,6 +106,8 @@ Manifest::Manifest(Location location, scoped_ptr<base::DictionaryValue> value)
type_(TYPE_UNKNOWN) {
if (value_->HasKey(keys::kTheme)) {
type_ = TYPE_THEME;
+ } else if (value_->HasKey(keys::kExport)) {
+ type_ = TYPE_SHARED_MODULE;
} else if (value_->HasKey(keys::kApp)) {
if (value_->Get(keys::kWebURLs, NULL) ||
value_->Get(keys::kLaunchWebURL, NULL)) {
diff --git a/chrome/common/extensions/manifest.h b/chrome/common/extensions/manifest.h
index c188793..7c37628 100644
--- a/chrome/common/extensions/manifest.h
+++ b/chrome/common/extensions/manifest.h
@@ -55,7 +55,8 @@ class Manifest {
// This is marked legacy because platform apps are preferred. For
// backwards compatibility, we can't remove support for packaged apps
TYPE_LEGACY_PACKAGED_APP,
- TYPE_PLATFORM_APP
+ TYPE_PLATFORM_APP,
+ TYPE_SHARED_MODULE
};
// Given two install sources, return the one which should take priority
@@ -122,6 +123,7 @@ class Manifest {
return type_ == TYPE_LEGACY_PACKAGED_APP;
}
bool is_extension() const { return type_ == TYPE_EXTENSION; }
+ bool is_shared_module() const { return type_ == TYPE_SHARED_MODULE; }
// These access the wrapped manifest value, returning false when the property
// does not exist or if the manifest type can't access it.
diff --git a/chrome/common/extensions/manifest_handlers/shared_module_info.cc b/chrome/common/extensions/manifest_handlers/shared_module_info.cc
new file mode 100644
index 0000000..3ead016
--- /dev/null
+++ b/chrome/common/extensions/manifest_handlers/shared_module_info.cc
@@ -0,0 +1,229 @@
+// Copyright (c) 2013 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/common/extensions/manifest_handlers/shared_module_info.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/version.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/permissions/permission_set.h"
+#include "extensions/common/error_utils.h"
+
+using base::DictionaryValue;
+namespace keys = extension_manifest_keys;
+namespace values = extension_manifest_values;
+namespace errors = extension_manifest_errors;
+
+namespace extensions {
+
+namespace {
+
+const char kSharedModule[] = "shared_module";
+
+static base::LazyInstance<SharedModuleInfo> g_empty_shared_module_info =
+ LAZY_INSTANCE_INITIALIZER;
+
+const SharedModuleInfo& GetSharedModuleInfo(const Extension* extension) {
+ SharedModuleInfo* info = static_cast<SharedModuleInfo*>(
+ extension->GetManifestData(kSharedModule));
+ if (!info)
+ return g_empty_shared_module_info.Get();
+ return *info;
+}
+
+} // namespace
+
+SharedModuleInfo::SharedModuleInfo() {
+}
+
+SharedModuleInfo::~SharedModuleInfo() {
+}
+
+// static
+void SharedModuleInfo::ParseImportedPath(const std::string& path,
+ std::string* import_id,
+ std::string* import_relative_path) {
+ std::vector<std::string> tokens;
+ Tokenize(path, std::string("/"), &tokens);
+ if (tokens.size() > 2 && tokens[0] == extension_filenames::kModulesDir &&
+ Extension::IdIsValid(tokens[1])) {
+ *import_id = tokens[1];
+ *import_relative_path = tokens[2];
+ for (size_t i = 3; i < tokens.size(); ++i)
+ *import_relative_path += "/" + tokens[i];
+ }
+}
+
+// static
+bool SharedModuleInfo::IsImportedPath(const std::string& path) {
+ std::vector<std::string> tokens;
+ Tokenize(path, std::string("/"), &tokens);
+ if (tokens.size() > 2 && tokens[0] == extension_filenames::kModulesDir &&
+ Extension::IdIsValid(tokens[1])) {
+ return true;
+ }
+ return false;
+}
+
+// static
+bool SharedModuleInfo::IsSharedModule(const Extension* extension) {
+ CHECK(extension);
+ return extension->manifest()->is_shared_module();
+}
+
+// static
+bool SharedModuleInfo::IsExportAllowed(const Extension* extension,
+ const std::string& relative_path) {
+ return GetSharedModuleInfo(extension).
+ exported_set_.MatchesURL(extension->url().Resolve(relative_path));
+}
+
+// static
+bool SharedModuleInfo::ImportsExtensionById(const Extension* extension,
+ const std::string& other_id) {
+ const SharedModuleInfo& info = GetSharedModuleInfo(extension);
+ for (size_t i = 0; i < info.imports_.size(); i++) {
+ if (info.imports_[i].extension_id == other_id)
+ return true;
+ }
+ return false;
+}
+
+// static
+bool SharedModuleInfo::ImportsModules(const Extension* extension) {
+ return GetSharedModuleInfo(extension).imports_.size() > 0;
+}
+
+// static
+const std::vector<SharedModuleInfo::ImportInfo>& SharedModuleInfo::GetImports(
+ const Extension* extension) {
+ return GetSharedModuleInfo(extension).imports_;
+}
+
+bool SharedModuleInfo::Parse(const Extension* extension, string16* error) {
+ bool has_import = extension->manifest()->HasKey(keys::kImport);
+ bool has_export = extension->manifest()->HasKey(keys::kExport);
+ if (!has_import && !has_export)
+ return true;
+
+ if (has_import && has_export) {
+ *error = ASCIIToUTF16(errors::kInvalidImportAndExport);
+ return false;
+ }
+
+ if (has_export) {
+ const DictionaryValue* export_value = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kExport, &export_value)) {
+ *error = ASCIIToUTF16(errors::kInvalidExport);
+ return false;
+ }
+ const ListValue* resources_list = NULL;
+ if (!export_value->GetList(keys::kResources, &resources_list)) {
+ *error = ASCIIToUTF16(errors::kInvalidExportResources);
+ return false;
+ }
+ for (size_t i = 0; i < resources_list->GetSize(); ++i) {
+ std::string resource_path;
+ if (!resources_list->GetString(i, &resource_path)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidExportResourcesString, base::IntToString(i));
+ return false;
+ }
+ const GURL& resolved_path = extension->url().Resolve(resource_path);
+ if (!resolved_path.is_valid()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidExportResourcesString, base::IntToString(i));
+ return false;
+ }
+ exported_set_.AddPattern(
+ URLPattern(URLPattern::SCHEME_EXTENSION, resolved_path.spec()));
+ }
+ }
+
+ if (has_import) {
+ const ListValue* import_list = NULL;
+ if (!extension->manifest()->GetList(keys::kImport, &import_list)) {
+ *error = ASCIIToUTF16(errors::kInvalidImport);
+ return false;
+ }
+ for (size_t i = 0; i < import_list->GetSize(); ++i) {
+ const DictionaryValue* import_entry = NULL;
+ if (!import_list->GetDictionary(i, &import_entry)) {
+ *error = ASCIIToUTF16(errors::kInvalidImport);
+ return false;
+ }
+ std::string extension_id;
+ imports_.push_back(ImportInfo());
+ if (!import_entry->GetString(keys::kId, &extension_id) ||
+ !Extension::IdIsValid(extension_id)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidImportId, base::IntToString(i));
+ return false;
+ }
+ imports_.back().extension_id = extension_id;
+ if (import_entry->HasKey(keys::kMinimumVersion)) {
+ std::string min_version;
+ if (!import_entry->GetString(keys::kMinimumVersion, &min_version)) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidImportVersion, base::IntToString(i));
+ return false;
+ }
+ imports_.back().minimum_version = min_version;
+ Version v(min_version);
+ if (!v.IsValid()) {
+ *error = ErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidImportVersion, base::IntToString(i));
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+SharedModuleHandler::SharedModuleHandler() {
+}
+
+SharedModuleHandler::~SharedModuleHandler() {
+}
+
+bool SharedModuleHandler::Parse(Extension* extension, string16* error) {
+ scoped_ptr<SharedModuleInfo> info(new SharedModuleInfo);
+ if (!info->Parse(extension, error))
+ return false;
+ extension->SetManifestData(kSharedModule, info.release());
+ return true;
+}
+
+bool SharedModuleHandler::Validate(
+ const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const {
+ // Extensions that export resources should not have any permissions of their
+ // own, instead they rely on the permissions of the extensions which import
+ // them.
+ if (SharedModuleInfo::IsSharedModule(extension) &&
+ !extension->GetActivePermissions()->IsEmpty()) {
+ *error = errors::kInvalidExportPermissions;
+ return false;
+ }
+ return true;
+}
+
+const std::vector<std::string> SharedModuleHandler::Keys() const {
+ static const char* keys[] = {
+ keys::kExport,
+ keys::kImport
+ };
+ return std::vector<std::string>(keys, keys + arraysize(keys));
+}
+
+} // extensions
+
diff --git a/chrome/common/extensions/manifest_handlers/shared_module_info.h b/chrome/common/extensions/manifest_handlers/shared_module_info.h
new file mode 100644
index 0000000..f76725a
--- /dev/null
+++ b/chrome/common/extensions/manifest_handlers/shared_module_info.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2013 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 CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_SHARED_MODULE_INFO_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_SHARED_MODULE_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/manifest_handler.h"
+
+namespace extensions {
+
+class SharedModuleInfo : public Extension::ManifestData {
+ public:
+ SharedModuleInfo();
+ virtual ~SharedModuleInfo();
+
+ bool Parse(const Extension* extension, string16* error);
+
+ struct ImportInfo {
+ std::string extension_id;
+ std::string minimum_version;
+ };
+
+ // Utility functions.
+ static void ParseImportedPath(const std::string& path,
+ std::string* import_id,
+ std::string* import_relative_path);
+ static bool IsImportedPath(const std::string& path);
+
+ // Functions relating to exporting resources.
+ static bool IsSharedModule(const Extension* extension);
+ static bool IsExportAllowed(const Extension* extension,
+ const std::string& relative_path);
+
+ // Functions relating to importing resources.
+ static bool ImportsExtensionById(const Extension* extension,
+ const std::string& other_id);
+ static bool ImportsModules(const Extension* extension);
+ static const std::vector<ImportInfo>& GetImports(const Extension* extension);
+
+ private:
+ // This extension exports the following resources to other extensions.
+ URLPatternSet exported_set_;
+
+ // Optional list of module imports of other extensions.
+ std::vector<ImportInfo> imports_;
+};
+
+// Parses all import/export keys in the manifest.
+class SharedModuleHandler : public ManifestHandler {
+ public:
+ SharedModuleHandler();
+ virtual ~SharedModuleHandler();
+
+ virtual bool Parse(Extension* extension, string16* error) OVERRIDE;
+ virtual bool Validate(const Extension* extension,
+ std::string* error,
+ std::vector<InstallWarning>* warnings) const OVERRIDE;
+
+ private:
+ virtual const std::vector<std::string> Keys() const OVERRIDE;
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_HANDLERS_SHARED_MODULE_INFO_H_
diff --git a/chrome/common/extensions/manifest_handlers/shared_module_manifest_unittest.cc b/chrome/common/extensions/manifest_handlers/shared_module_manifest_unittest.cc
new file mode 100644
index 0000000..b4eb0bd
--- /dev/null
+++ b/chrome/common/extensions/manifest_handlers/shared_module_manifest_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2013 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 "base/version.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_unittest.h"
+#include "chrome/common/extensions/manifest_handlers/shared_module_info.h"
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char* kValidImportPath =
+ "_modules/abcdefghijklmnopabcdefghijklmnop/foo/bar.html";
+const char* kValidImportPathID = "abcdefghijklmnopabcdefghijklmnop";
+const char* kValidImportPathRelative = "foo/bar.html";
+const char* kInvalidImportPath = "_modules/abc/foo.html";
+const char* kImportId1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char* kImportId2 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+const char* kNoImport = "cccccccccccccccccccccccccccccccc";
+
+}
+
+namespace extensions {
+
+class SharedModuleManifestTest : public ExtensionManifestTest {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ExtensionManifestTest::SetUp();
+ (new SharedModuleHandler)->Register();
+ }
+};
+
+TEST_F(SharedModuleManifestTest, ExportsAll) {
+ Manifest manifest("shared_module_export.json");
+
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(manifest);
+
+ EXPECT_TRUE(SharedModuleInfo::IsSharedModule(extension)) << manifest.name();
+ EXPECT_FALSE(SharedModuleInfo::ImportsModules(extension)) << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowed(extension, "foo"))
+ << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowed(extension, "foo/bar"))
+ << manifest.name();
+}
+
+TEST_F(SharedModuleManifestTest, ExportFoo) {
+ Manifest manifest("shared_module_export_foo.json");
+
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(manifest);
+
+ EXPECT_TRUE(SharedModuleInfo::IsSharedModule(extension)) << manifest.name();
+ EXPECT_FALSE(SharedModuleInfo::ImportsModules(extension)) << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::IsExportAllowed(extension, "foo"))
+ << manifest.name();
+ EXPECT_FALSE(SharedModuleInfo::IsExportAllowed(extension, "foo/bar"))
+ << manifest.name();
+}
+
+TEST_F(SharedModuleManifestTest, ExportParseErrors) {
+ Testcase testcases[] = {
+ Testcase("shared_module_export_and_import.json",
+ "Simultaneous 'import' and 'export' are not allowed."),
+ Testcase("shared_module_export_not_dict.json",
+ "Invalid value for 'export'."),
+ Testcase("shared_module_export_resources_not_list.json",
+ "Invalid value for 'export.resources'."),
+ Testcase("shared_module_export_resource_not_string.json",
+ "Invalid value for 'export.resources[1]'."),
+ };
+ RunTestcases(testcases, arraysize(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(SharedModuleManifestTest, SharedModuleStaticFunctions) {
+ EXPECT_TRUE(SharedModuleInfo::IsImportedPath(kValidImportPath));
+ EXPECT_FALSE(SharedModuleInfo::IsImportedPath(kInvalidImportPath));
+
+ std::string id;
+ std::string relative;
+ SharedModuleInfo::ParseImportedPath(kValidImportPath, &id, &relative);
+ EXPECT_EQ(id, kValidImportPathID);
+ EXPECT_EQ(relative, kValidImportPathRelative);
+}
+
+TEST_F(SharedModuleManifestTest, Import) {
+ Manifest manifest("shared_module_import.json");
+
+ scoped_refptr<Extension> extension = LoadAndExpectSuccess(manifest);
+
+ EXPECT_FALSE(SharedModuleInfo::IsSharedModule(extension)) << manifest.name();
+ EXPECT_TRUE(SharedModuleInfo::ImportsModules(extension)) << manifest.name();
+ const std::vector<SharedModuleInfo::ImportInfo>& imports =
+ SharedModuleInfo::GetImports(extension);
+ ASSERT_EQ(2U, imports.size());
+ EXPECT_EQ(imports[0].extension_id, kImportId1);
+ EXPECT_EQ(imports[0].minimum_version, "");
+ EXPECT_EQ(imports[1].extension_id, kImportId2);
+ EXPECT_TRUE(base::Version(imports[1].minimum_version).IsValid());
+ EXPECT_TRUE(SharedModuleInfo::ImportsExtensionById(extension, kImportId1));
+ EXPECT_TRUE(SharedModuleInfo::ImportsExtensionById(extension, kImportId2));
+ EXPECT_FALSE(SharedModuleInfo::ImportsExtensionById(extension, kNoImport));
+}
+
+TEST_F(SharedModuleManifestTest, ImportParseErrors) {
+ Testcase testcases[] = {
+ Testcase("shared_module_import_not_list.json",
+ "Invalid value for 'import'."),
+ Testcase("shared_module_import_invalid_id.json",
+ "Invalid value for 'import[0].id'."),
+ Testcase("shared_module_import_invalid_version.json",
+ "Invalid value for 'import[0].minimum_version'."),
+ };
+ RunTestcases(testcases, arraysize(testcases), EXPECT_TYPE_ERROR);
+}
+
+} // namespace extensions
diff --git a/chrome/common/extensions/manifest_unittest.cc b/chrome/common/extensions/manifest_unittest.cc
index 6805022..8845ae0 100644
--- a/chrome/common/extensions/manifest_unittest.cc
+++ b/chrome/common/extensions/manifest_unittest.cc
@@ -36,6 +36,8 @@ class ManifestTest : public testing::Test {
EXPECT_EQ(type == Manifest::TYPE_LEGACY_PACKAGED_APP,
manifest->is_legacy_packaged_app());
EXPECT_EQ(type == Manifest::TYPE_HOSTED_APP, manifest->is_hosted_app());
+ EXPECT_EQ(type == Manifest::TYPE_SHARED_MODULE,
+ manifest->is_shared_module());
}
// Helper function that replaces the Manifest held by |manifest| with a copy
@@ -138,6 +140,13 @@ TEST_F(ManifestTest, ExtensionTypes) {
MutateManifest(
&manifest, keys::kTheme, NULL);
+ // Shared module.
+ MutateManifest(
+ &manifest, keys::kExport, new base::DictionaryValue());
+ AssertType(manifest.get(), Manifest::TYPE_SHARED_MODULE);
+ MutateManifest(
+ &manifest, keys::kExport, NULL);
+
// Packaged app.
MutateManifest(
&manifest, keys::kApp, new base::DictionaryValue());
diff --git a/chrome/test/data/extensions/api_test/shared_module/import_non_existent/manifest.json b/chrome/test/data/extensions/api_test/shared_module/import_non_existent/manifest.json
new file mode 100644
index 0000000..87b0a70
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/import_non_existent/manifest.json
@@ -0,0 +1,9 @@
+{
+ "name": "Shared Module Import Non Existent",
+ "manifest_version": 2,
+ "version": "1.0",
+ "import": [{
+ "id": "cccccccccccccccccccccccccccccccc"
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/shared_module/import_pass/check.js b/chrome/test/data/extensions/api_test/shared_module/import_pass/check.js
new file mode 100644
index 0000000..37ca2a0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/import_pass/check.js
@@ -0,0 +1,11 @@
+// Copyright (c) 2013 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.
+
+if (typeof fail_exported != 'undefined')
+ chrome.test.notifyFail('fail.js was unintentionally exported.');
+
+if (typeof pass_exported == 'undefined')
+ chrome.test.notifyFail('pass.js was not exported correctly.');
+
+chrome.test.notifyPass();
diff --git a/chrome/test/data/extensions/api_test/shared_module/import_pass/main.html b/chrome/test/data/extensions/api_test/shared_module/import_pass/main.html
new file mode 100644
index 0000000..1c412ad
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/import_pass/main.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<!-- fail.js is not exported, it would be an error to have it load. -->
+<script src="_modules/gpcckkmippodnppallflahfabmeilgjg/fail.js"></script>
+<script src="_modules/gpcckkmippodnppallflahfabmeilgjg/pass.js"></script>
+<script src="check.js"></script>
diff --git a/chrome/test/data/extensions/api_test/shared_module/import_pass/manifest.json b/chrome/test/data/extensions/api_test/shared_module/import_pass/manifest.json
new file mode 100644
index 0000000..a9ed377
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/import_pass/manifest.json
@@ -0,0 +1,14 @@
+{
+ "name": "Shared Module Import Test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "app": {
+ "background": {
+ "page": "main.html"
+ }
+ },
+ "import": [{
+ "id": "gpcckkmippodnppallflahfabmeilgjg"
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/shared_module/import_wrong_version/manifest.json b/chrome/test/data/extensions/api_test/shared_module/import_wrong_version/manifest.json
new file mode 100644
index 0000000..5d815b5
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/import_wrong_version/manifest.json
@@ -0,0 +1,10 @@
+{
+ "name": "Shared Module Import Test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "import": [{
+ "id": "gpcckkmippodnppallflahfabmeilgjg",
+ "minimum_version": "2.0"
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/shared_module/shared/fail.js b/chrome/test/data/extensions/api_test/shared_module/shared/fail.js
new file mode 100644
index 0000000..dbe933d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/shared/fail.js
@@ -0,0 +1,5 @@
+// Copyright (c) 2013 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.
+
+var fail_exported = {};
diff --git a/chrome/test/data/extensions/api_test/shared_module/shared/manifest.json b/chrome/test/data/extensions/api_test/shared_module/shared/manifest.json
new file mode 100644
index 0000000..6a90fa4
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/shared/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "Shared Module Test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "export": {
+ "resources": [
+ "pass.js"
+ ]
+ },
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq2ADgfXh2Yh6uLgpBrls/gtQEp5gLWZedwRV1Cyy4R1giRnGeYwZ8pgmtpkjdSfoNIrx/WfiXDF+K2TF2I9V+LM60FqqoY6uJNmXjByzKFjNtFETXthA+v/zv6uX1NJ3m+GN9tdDR53AC7ws28Zi0S4+n/a++uzDY+aDubrp+PwIDAQAB"
+}
diff --git a/chrome/test/data/extensions/api_test/shared_module/shared/pass.js b/chrome/test/data/extensions/api_test/shared_module/shared/pass.js
new file mode 100644
index 0000000..2e54dea
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/shared_module/shared/pass.js
@@ -0,0 +1,5 @@
+// Copyright (c) 2013 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.
+
+var pass_exported = {};
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_export.json b/chrome/test/data/extensions/manifest_tests/shared_module_export.json
new file mode 100644
index 0000000..aafb5f8
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_export.json
@@ -0,0 +1,11 @@
+{
+ "name": "shared_module_export unittest",
+ "manifest_version": 2,
+ "version": "1.0",
+ "export": {
+ "resources": [
+ "*"
+ ]
+ },
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq2ADgfXh2Yh6uLgpBrls/gtQEp5gLWZedwRV1Cyy4R1giRnGeYwZ8pgmtpkjdSfoNIrx/WfiXDF+K2TF2I9V+LM60FqqoY6uJNmXjByzKFjNtFETXthA+v/zv6uX1NJ3m+GN9tdDR53AC7ws28Zi0S4+n/a++uzDY+aDubrp+PwIDAQAB"
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_export_and_import.json b/chrome/test/data/extensions/manifest_tests/shared_module_export_and_import.json
new file mode 100644
index 0000000..3f59ac2
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_export_and_import.json
@@ -0,0 +1,14 @@
+{
+ "name": "shared_module_export unittest",
+ "manifest_version": 2,
+ "version": "1.0",
+ "export": {
+ "resources": [
+ "*"
+ ]
+ },
+ "import": [{
+ "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ }],
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq2ADgfXh2Yh6uLgpBrls/gtQEp5gLWZedwRV1Cyy4R1giRnGeYwZ8pgmtpkjdSfoNIrx/WfiXDF+K2TF2I9V+LM60FqqoY6uJNmXjByzKFjNtFETXthA+v/zv6uX1NJ3m+GN9tdDR53AC7ws28Zi0S4+n/a++uzDY+aDubrp+PwIDAQAB"
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_export_foo.json b/chrome/test/data/extensions/manifest_tests/shared_module_export_foo.json
new file mode 100644
index 0000000..e0b583f
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_export_foo.json
@@ -0,0 +1,11 @@
+{
+ "name": "shared_module_export unittest",
+ "manifest_version": 2,
+ "version": "1.0",
+ "export": {
+ "resources": [
+ "foo"
+ ]
+ },
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq2ADgfXh2Yh6uLgpBrls/gtQEp5gLWZedwRV1Cyy4R1giRnGeYwZ8pgmtpkjdSfoNIrx/WfiXDF+K2TF2I9V+LM60FqqoY6uJNmXjByzKFjNtFETXthA+v/zv6uX1NJ3m+GN9tdDR53AC7ws28Zi0S4+n/a++uzDY+aDubrp+PwIDAQAB"
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_export_not_dict.json b/chrome/test/data/extensions/manifest_tests/shared_module_export_not_dict.json
new file mode 100644
index 0000000..2500644
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_export_not_dict.json
@@ -0,0 +1,9 @@
+{
+ "name": "shared_module_export unittest",
+ "manifest_version": 2,
+ "version": "1.0",
+ "export": [
+ {"resources": "*"}
+ ],
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq2ADgfXh2Yh6uLgpBrls/gtQEp5gLWZedwRV1Cyy4R1giRnGeYwZ8pgmtpkjdSfoNIrx/WfiXDF+K2TF2I9V+LM60FqqoY6uJNmXjByzKFjNtFETXthA+v/zv6uX1NJ3m+GN9tdDR53AC7ws28Zi0S4+n/a++uzDY+aDubrp+PwIDAQAB"
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_export_resource_not_string.json b/chrome/test/data/extensions/manifest_tests/shared_module_export_resource_not_string.json
new file mode 100644
index 0000000..2021553
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_export_resource_not_string.json
@@ -0,0 +1,12 @@
+{
+ "name": "shared_module_export unittest",
+ "manifest_version": 2,
+ "version": "1.0",
+ "export": {
+ "resources": [
+ "*",
+ {}
+ ]
+ },
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq2ADgfXh2Yh6uLgpBrls/gtQEp5gLWZedwRV1Cyy4R1giRnGeYwZ8pgmtpkjdSfoNIrx/WfiXDF+K2TF2I9V+LM60FqqoY6uJNmXjByzKFjNtFETXthA+v/zv6uX1NJ3m+GN9tdDR53AC7ws28Zi0S4+n/a++uzDY+aDubrp+PwIDAQAB"
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_export_resources_not_list.json b/chrome/test/data/extensions/manifest_tests/shared_module_export_resources_not_list.json
new file mode 100644
index 0000000..76971072
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_export_resources_not_list.json
@@ -0,0 +1,9 @@
+{
+ "name": "shared_module_export unittest",
+ "manifest_version": 2,
+ "version": "1.0",
+ "export": {
+ "resources": "*"
+ },
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq2ADgfXh2Yh6uLgpBrls/gtQEp5gLWZedwRV1Cyy4R1giRnGeYwZ8pgmtpkjdSfoNIrx/WfiXDF+K2TF2I9V+LM60FqqoY6uJNmXjByzKFjNtFETXthA+v/zv6uX1NJ3m+GN9tdDR53AC7ws28Zi0S4+n/a++uzDY+aDubrp+PwIDAQAB"
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_import.json b/chrome/test/data/extensions/manifest_tests/shared_module_import.json
new file mode 100644
index 0000000..ca9e170
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_import.json
@@ -0,0 +1,14 @@
+{
+ "name": "Import unit test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "import": [
+ {
+ "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ },
+ {
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "minimum_version": "1.0.0.0"
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_id.json b/chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_id.json
new file mode 100644
index 0000000..aadd9ae
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_id.json
@@ -0,0 +1,10 @@
+{
+ "name": "Import unit test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "import": [
+ {
+ "id": "xyz"
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_version.json b/chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_version.json
new file mode 100644
index 0000000..77a921c
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_import_invalid_version.json
@@ -0,0 +1,11 @@
+{
+ "name": "Import unit test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "import": [
+ {
+ "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "minimum_version": "aaa"
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/manifest_tests/shared_module_import_not_list.json b/chrome/test/data/extensions/manifest_tests/shared_module_import_not_list.json
new file mode 100644
index 0000000..a989bd9
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/shared_module_import_not_list.json
@@ -0,0 +1,8 @@
+{
+ "name": "Import unit test",
+ "manifest_version": 2,
+ "version": "1.0",
+ "import": {
+ "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ }
+}