summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorjstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-02 23:53:46 +0000
committerjstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-02 23:53:46 +0000
commitf66a50a51caad37a29fd51eb26b14d96cc9f51eb (patch)
tree929cacefa5c3ca6a70c308f8f8859e5fdf82ea2f /chrome
parent47d2f41e5f17f1daab75668b742204d61fba014a (diff)
downloadchromium_src-f66a50a51caad37a29fd51eb26b14d96cc9f51eb.zip
chromium_src-f66a50a51caad37a29fd51eb26b14d96cc9f51eb.tar.gz
chromium_src-f66a50a51caad37a29fd51eb26b14d96cc9f51eb.tar.bz2
Adds a webstorePrivate method for silently installing extensions.
BUG=98687 TEST=*WebstorePrivate* Review URL: http://codereview.chromium.org/8430033 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@108367 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/crx_installer.cc5
-rw-r--r--chrome/browser/extensions/crx_installer.h3
-rw-r--r--chrome/browser/extensions/extension_browsertest.cc17
-rw-r--r--chrome/browser/extensions/extension_browsertest.h9
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc1
-rw-r--r--chrome/browser/extensions/extension_install_ui.cc6
-rw-r--r--chrome/browser/extensions/extension_install_ui.h8
-rw-r--r--chrome/browser/extensions/extension_webstore_private_api.cc115
-rw-r--r--chrome/browser/extensions/extension_webstore_private_api.h35
-rw-r--r--chrome/browser/extensions/extension_webstore_private_apitest.cc51
-rw-r--r--chrome/browser/extensions/webstore_installer.cc75
-rw-r--r--chrome/browser/extensions/webstore_installer.h4
-rw-r--r--chrome/common/chrome_switches.cc4
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--chrome/common/extensions/api/extension_api.json32
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg.pem16
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg/manifest.json8
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk.pem16
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk/manifest.json5
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe.pem16
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/content_script.js6
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/manifest.json10
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/common.js15
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/silently_install.html71
24 files changed, 504 insertions, 25 deletions
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index a8b39ca..c650daa 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -58,7 +58,8 @@ static base::LazyInstance<Whitelist>
} // namespace
CrxInstaller::WhitelistEntry::WhitelistEntry()
- : use_app_installed_bubble(false) {}
+ : use_app_installed_bubble(false),
+ skip_post_install_ui(false) {}
CrxInstaller::WhitelistEntry::~WhitelistEntry() {}
// static
@@ -417,6 +418,8 @@ void CrxInstaller::ConfirmInstall() {
whitelisted = true;
if (entry->use_app_installed_bubble)
client_->set_use_app_installed_bubble(true);
+ if (entry->skip_post_install_ui)
+ client_->set_skip_post_install_ui(true);
}
if (client_ &&
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index d2955ae..4938388 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -75,6 +75,9 @@ class CrxInstaller
// Whether to use a bubble notification when an app is installed, instead of
// the default behavior of transitioning to the new tab page.
bool use_app_installed_bubble;
+
+ // Whether to skip the post install UI like the extension installed bubble.
+ bool skip_post_install_ui;
};
// Exempt the next extension install with |id| from displaying a confirmation
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 771b1ad..c49be53 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -170,16 +170,29 @@ FilePath ExtensionBrowserTest::PackExtension(const FilePath& dir_path) {
return FilePath();
}
+ return PackExtensionWithOptions(dir_path, crx_path, FilePath(), pem_path);
+}
+
+FilePath ExtensionBrowserTest::PackExtensionWithOptions(
+ const FilePath& dir_path,
+ const FilePath& crx_path,
+ const FilePath& pem_path,
+ const FilePath& pem_out_path) {
if (!file_util::PathExists(dir_path)) {
ADD_FAILURE() << "Extension dir not found: " << dir_path.value();
return FilePath();
}
+ if (!file_util::PathExists(pem_path) && pem_out_path.empty()) {
+ ADD_FAILURE() << "Must specify a PEM file or PEM output path";
+ return FilePath();
+ }
+
scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
if (!creator->Run(dir_path,
crx_path,
- FilePath(), // no existing pem, use empty path
- pem_path)) {
+ pem_path,
+ pem_out_path)) {
ADD_FAILURE() << "ExtensionCreator::Run() failed: "
<< creator->error_message();
return FilePath();
diff --git a/chrome/browser/extensions/extension_browsertest.h b/chrome/browser/extensions/extension_browsertest.h
index 30645ed..10e0d8a 100644
--- a/chrome/browser/extensions/extension_browsertest.h
+++ b/chrome/browser/extensions/extension_browsertest.h
@@ -43,6 +43,15 @@ class ExtensionBrowserTest
// Return an empty FilePath if there were errors.
FilePath PackExtension(const FilePath& dir_path);
+ // Pack the extension in |dir_path| into a crx file at |crx_path|, using the
+ // key |pem_path|. If |pem_path| does not exist, create a new key at
+ // |pem_out_path|.
+ // Return the path to the crx file, or an empty FilePath if there were errors.
+ FilePath PackExtensionWithOptions(const FilePath& dir_path,
+ const FilePath& crx_path,
+ const FilePath& pem_path,
+ const FilePath& pem_out_path);
+
// |expected_change| indicates how many extensions should be installed (or
// disabled, if negative).
// 1 means you expect a new install, 0 means you expect an upgrade, -1 means
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 178487b..5e8b4bf 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -359,6 +359,7 @@ void FactoryRegistry::ResetFunctions() {
RegisterFunction<SetStoreLoginFunction>();
RegisterFunction<BeginInstallWithManifestFunction>();
RegisterFunction<CompleteInstallFunction>();
+ RegisterFunction<SilentlyInstallFunction>();
// WebNavigation.
RegisterFunction<GetFrameFunction>();
diff --git a/chrome/browser/extensions/extension_install_ui.cc b/chrome/browser/extensions/extension_install_ui.cc
index 409e6dd..5c04315 100644
--- a/chrome/browser/extensions/extension_install_ui.cc
+++ b/chrome/browser/extensions/extension_install_ui.cc
@@ -198,7 +198,8 @@ ExtensionInstallUI::ExtensionInstallUI(Profile* profile)
delegate_(NULL),
prompt_type_(NUM_PROMPT_TYPES),
ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)),
- use_app_installed_bubble_(false) {
+ use_app_installed_bubble_(false),
+ skip_post_install_ui_(false) {
// Remember the current theme in case the user presses undo.
if (profile_) {
const Extension* previous_theme =
@@ -255,6 +256,9 @@ void ExtensionInstallUI::ConfirmPermissions(
void ExtensionInstallUI::OnInstallSuccess(const Extension* extension,
SkBitmap* icon) {
+ if (skip_post_install_ui_)
+ return;
+
extension_ = extension;
SetIcon(icon);
diff --git a/chrome/browser/extensions/extension_install_ui.h b/chrome/browser/extensions/extension_install_ui.h
index 69dc33fd..93d4130 100644
--- a/chrome/browser/extensions/extension_install_ui.h
+++ b/chrome/browser/extensions/extension_install_ui.h
@@ -116,6 +116,11 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer {
use_app_installed_bubble_ = use_bubble;
}
+ // Whether or not to show the default UI after completing the installation.
+ void set_skip_post_install_ui(bool is_bundle) {
+ skip_post_install_ui_ = is_bundle;
+ }
+
// This is called by the installer to verify whether the installation should
// proceed. This is declared virtual for testing.
//
@@ -214,6 +219,9 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer {
// Whether to show an installed bubble on app install, or use the default
// action of opening a new tab page.
bool use_app_installed_bubble_;
+
+ // Whether or not to show the default UI after completing the installation.
+ bool skip_post_install_ui_;
};
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_INSTALL_UI_H_
diff --git a/chrome/browser/extensions/extension_webstore_private_api.cc b/chrome/browser/extensions/extension_webstore_private_api.cc
index 3e9a605..d76da49 100644
--- a/chrome/browser/extensions/extension_webstore_private_api.cc
+++ b/chrome/browser/extensions/extension_webstore_private_api.cc
@@ -52,6 +52,8 @@ const char kInvalidManifestError[] = "Invalid manifest";
const char kNoPreviousBeginInstallWithManifestError[] =
"* does not match a previous call to beginInstallWithManifest3";
const char kUserCancelledError[] = "User cancelled install";
+const char kPermissionDeniedError[] =
+ "You do not have permission to use this method.";
ProfileSyncService* test_sync_service = NULL;
@@ -73,6 +75,31 @@ bool IsWebStoreURL(Profile* profile, const GURL& url) {
return (service->GetExtensionByWebExtent(url) == store);
}
+// Whitelists extension IDs for use by webstorePrivate.silentlyInstall.
+bool trust_test_ids = false;
+
+bool IsTrustedForSilentInstall(const std::string& id) {
+ // Trust the extensions in api_test/webstore_private/bundle when the flag
+ // is set.
+ if (trust_test_ids &&
+ (id == "begfmnajjkbjdgmffnjaojchoncnmngg" ||
+ id == "bmfoocgfinpmkmlbjhcbofejhkhlbchk" ||
+ id == "mpneghmdnmaolkljkipbhaienajcflfe"))
+ return true;
+
+ return
+ id == "jgoepmocgafhnchmokaimcmlojpnlkhp" || // +1 Extension
+ id == "cpembckmhnjipbgbnfiocbgnkpjdokdd" || // +1 Extension - dev
+ id == "boemmnepglcoinjcdlfcpcbmhiecichi" || // Notifications
+ id == "flibmgiapaohcbondaoopaalfejliklp" || // Notifications - dev
+ id == "dlppkpafhbajpcmmoheippocdidnckmm" || // Remaining are placeholders
+ id == "hmglfmpefabcafaimbpldpambdfomanl" ||
+ id == "idfijlieiecpfcjckpkliefekpokhhnd" ||
+ id == "jaokjbijaokooelpahnlmbciccldmfla" ||
+ id == "kdjeommiakphmeionoojjljlecmpaldd" ||
+ id == "lpdeojkfhenboeibhkjhiancceeboknd";
+}
+
// Helper to create a dictionary with login and token properties set from
// the appropriate values in the passed-in |profile|.
DictionaryValue* CreateLoginResult(Profile* profile) {
@@ -109,6 +136,11 @@ void WebstorePrivateApi::SetWebstoreInstallerDelegateForTesting(
test_webstore_installer_delegate = delegate;
}
+// static
+void WebstorePrivateApi::SetTrustTestIDsForTesting(bool allow) {
+ trust_test_ids = allow;
+}
+
BeginInstallWithManifestFunction::BeginInstallWithManifestFunction()
: use_app_installed_bubble_(false) {}
@@ -345,6 +377,89 @@ bool CompleteInstallFunction::RunImpl() {
return true;
}
+SilentlyInstallFunction::SilentlyInstallFunction() {}
+SilentlyInstallFunction::~SilentlyInstallFunction() {}
+
+bool SilentlyInstallFunction::RunImpl() {
+ if (!IsWebStoreURL(profile_, source_url())) {
+ error_ = kPermissionDeniedError;
+ return false;
+ }
+
+ DictionaryValue* details = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details));
+ CHECK(details);
+
+ EXTENSION_FUNCTION_VALIDATE(details->GetString(kIdKey, &id_));
+ if (!IsTrustedForSilentInstall(id_)) {
+ error_ = kInvalidIdError;
+ return false;
+ }
+
+ EXTENSION_FUNCTION_VALIDATE(details->GetString(kManifestKey, &manifest_));
+
+ // Matched in OnWebstoreParseFailure, OnExtensionInstall{Success,Failure}.
+ AddRef();
+
+ scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
+ this, id_, manifest_, std::string(), GURL(), NULL);
+ helper->Start();
+
+ return true;
+}
+
+void SilentlyInstallFunction::OnWebstoreParseSuccess(
+ const std::string& id,
+ const SkBitmap& icon,
+ base::DictionaryValue* parsed_manifest) {
+ CHECK_EQ(id_, id);
+
+ // This lets CrxInstaller bypass the permission confirmation UI for the
+ // extension. The whitelist entry gets cleared in
+ // CrxInstaller::ConfirmInstall.
+ CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
+ entry->parsed_manifest.reset(parsed_manifest);
+ entry->use_app_installed_bubble = false;
+ entry->skip_post_install_ui = true;
+ CrxInstaller::SetWhitelistEntry(id_, entry);
+
+ scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
+ profile(), this,
+ &(dispatcher()->delegate()->GetAssociatedTabContents()->controller()),
+ id_, WebstoreInstaller::FLAG_NONE);
+ installer->Start();
+}
+
+void SilentlyInstallFunction::OnWebstoreParseFailure(
+ const std::string& id,
+ InstallHelperResultCode result_code,
+ const std::string& error_message) {
+ CHECK_EQ(id_, id);
+
+ error_ = error_message;
+ SendResponse(false);
+
+ Release(); // Matches the AddRef() in RunImpl().
+}
+
+void SilentlyInstallFunction::OnExtensionInstallSuccess(const std::string& id) {
+ CHECK_EQ(id_, id);
+
+ SendResponse(true);
+
+ Release(); // Matches the AddRef() in RunImpl().
+}
+
+void SilentlyInstallFunction::OnExtensionInstallFailure(
+ const std::string& id, const std::string& error) {
+ CHECK_EQ(id_, id);
+
+ error_ = error;
+ SendResponse(false);
+
+ Release(); // Matches the AddRef() in RunImpl().
+}
+
bool GetBrowserLoginFunction::RunImpl() {
if (!IsWebStoreURL(profile_, source_url()))
return false;
diff --git a/chrome/browser/extensions/extension_webstore_private_api.h b/chrome/browser/extensions/extension_webstore_private_api.h
index f1021e1..4dd9b82 100644
--- a/chrome/browser/extensions/extension_webstore_private_api.h
+++ b/chrome/browser/extensions/extension_webstore_private_api.h
@@ -27,6 +27,10 @@ class WebstorePrivateApi {
// Allows you to override the WebstoreInstaller delegate for testing.
static void SetWebstoreInstallerDelegateForTesting(
WebstoreInstaller::Delegate* delegate);
+
+ // If |allow| is true, then the extension IDs used by the SilentlyInstall
+ // apitest will be trusted.
+ static void SetTrustTestIDsForTesting(bool allow);
};
class BeginInstallWithManifestFunction
@@ -109,6 +113,37 @@ class CompleteInstallFunction : public SyncExtensionFunction {
DECLARE_EXTENSION_FUNCTION_NAME("webstorePrivate.completeInstall");
};
+class SilentlyInstallFunction : public AsyncExtensionFunction,
+ public WebstoreInstallHelper::Delegate,
+ public WebstoreInstaller::Delegate {
+ public:
+ SilentlyInstallFunction();
+
+ // WebstoreInstallHelper::Delegate implementation.
+ virtual void OnWebstoreParseSuccess(
+ const std::string& id,
+ const SkBitmap& icon,
+ base::DictionaryValue* parsed_manifest) OVERRIDE;
+ virtual void OnWebstoreParseFailure(
+ const std::string& id,
+ InstallHelperResultCode result_code,
+ const std::string& error_message) OVERRIDE;
+
+ // WebstoreInstaller::Delegate implementation.
+ virtual void OnExtensionInstallSuccess(const std::string& id) OVERRIDE;
+ virtual void OnExtensionInstallFailure(const std::string& id,
+ const std::string& error) OVERRIDE;
+
+ protected:
+ virtual ~SilentlyInstallFunction();
+ virtual bool RunImpl() OVERRIDE;
+
+ private:
+ std::string id_;
+ std::string manifest_;
+ DECLARE_EXTENSION_FUNCTION_NAME("webstorePrivate.silentlyInstall");
+};
+
class GetBrowserLoginFunction : public SyncExtensionFunction {
virtual bool RunImpl();
DECLARE_EXTENSION_FUNCTION_NAME("webstorePrivate.getBrowserLogin");
diff --git a/chrome/browser/extensions/extension_webstore_private_apitest.cc b/chrome/browser/extensions/extension_webstore_private_apitest.cc
index d7fbf65..18d49e4 100644
--- a/chrome/browser/extensions/extension_webstore_private_apitest.cc
+++ b/chrome/browser/extensions/extension_webstore_private_apitest.cc
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
#include "base/stringprintf.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_install_dialog.h"
@@ -125,6 +129,47 @@ class ExtensionWebstorePrivateApiTest : public ExtensionApiTest {
}
};
+class ExtensionWebstorePrivateBundleTest
+ : public ExtensionWebstorePrivateApiTest {
+ public:
+ void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ ExtensionWebstorePrivateApiTest::SetUpInProcessBrowserTestFixture();
+
+ // The test server needs to have already started, so setup the switch here
+ // rather than in SetUpCommandLine.
+ CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kAppsGalleryDownloadURL,
+ GetTestServerURL("bundle/%s.crx").spec());
+
+ PackCRX("begfmnajjkbjdgmffnjaojchoncnmngg");
+ PackCRX("bmfoocgfinpmkmlbjhcbofejhkhlbchk");
+ PackCRX("mpneghmdnmaolkljkipbhaienajcflfe");
+ }
+
+ void TearDownInProcessBrowserTestFixture() OVERRIDE {
+ ExtensionWebstorePrivateApiTest::TearDownInProcessBrowserTestFixture();
+ for (size_t i = 0; i < test_crx_.size(); ++i)
+ ASSERT_TRUE(file_util::Delete(test_crx_[i], false));
+ }
+
+ private:
+ void PackCRX(const std::string& id) {
+ FilePath data_path = test_data_dir_.AppendASCII("webstore_private/bundle");
+ FilePath dir_path = data_path.AppendASCII(id);
+ FilePath pem_path = data_path.AppendASCII(id + ".pem");
+ FilePath crx_path = data_path.AppendASCII(id + ".crx");
+ FilePath destination = PackExtensionWithOptions(
+ dir_path, crx_path, pem_path, FilePath());
+
+ ASSERT_FALSE(destination.empty());
+ ASSERT_EQ(destination, crx_path);
+
+ test_crx_.push_back(destination);
+ }
+
+ std::vector<FilePath> test_crx_;
+};
+
// Test cases where the user accepts the install confirmation dialog.
IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTest, InstallAccepted) {
ASSERT_TRUE(RunInstallTest("accepted.html", "extension.crx"));
@@ -178,3 +223,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTest,
IconUrl) {
ASSERT_TRUE(RunInstallTest("icon_url.html", "extension.crx"));
}
+
+// Tests using silentlyInstall to install extensions.
+IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateBundleTest, SilentlyInstall) {
+ WebstorePrivateApi::SetTrustTestIDsForTesting(true);
+ ASSERT_TRUE(RunPageTest(GetTestServerURL("silently_install.html").spec()));
+}
diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc
index a32f4c2..3939438 100644
--- a/chrome/browser/extensions/webstore_installer.cc
+++ b/chrome/browser/extensions/webstore_installer.cc
@@ -4,17 +4,27 @@
#include "chrome/browser/extensions/webstore_installer.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/stringprintf.h"
#include "base/string_util.h"
#include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_util.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
+#include "content/browser/download/download_file.h"
+#include "content/browser/download/download_manager.h"
+#include "content/browser/download/download_types.h"
#include "content/browser/tab_contents/navigation_controller.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "googleurl/src/gurl.h"
@@ -30,6 +40,13 @@ const char kDefaultInstallSource[] = "";
GURL GetWebstoreInstallURL(
const std::string& extension_id, const std::string& install_source) {
+ CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) {
+ std::string download_url =
+ cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL);
+ return GURL(base::StringPrintf(download_url.c_str(),
+ extension_id.c_str()));
+ }
std::vector<std::string> params;
params.push_back("id=" + extension_id);
if (!install_source.empty()) {
@@ -46,6 +63,20 @@ GURL GetWebstoreInstallURL(
return url;
}
+// Must be executed on the FILE thread.
+void GetDownloadFilePath(const std::string& id,
+ const base::Callback<void(FilePath)>& callback) {
+ FilePath file =
+ download_util::GetDefaultDownloadDirectory().AppendASCII(id + ".crx");
+
+ int uniquifier = DownloadFile::GetUniquePathNumber(file);
+ if (uniquifier > 0)
+ DownloadFile::AppendNumberToPath(&file, uniquifier);
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(callback, file));
+}
+
} // namespace
@@ -71,6 +102,7 @@ WebstoreInstaller::WebstoreInstaller(Profile* profile,
WebstoreInstaller::~WebstoreInstaller() {}
void WebstoreInstaller::Start() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
AddRef(); // Balanced in ReportSuccess and ReportFailure.
if (!Extension::IdIsValid(id_)) {
@@ -78,22 +110,11 @@ void WebstoreInstaller::Start() {
return;
}
- // TODO(mihaip): For inline installs, we pretend like the referrer is the
- // gallery, even though this could be an inline install, in order to pass the
- // checks in ExtensionService::IsDownloadFromGallery. We should instead pass
- // the real referrer, track if this is an inline install in the whitelist
- // entry and look that up when checking that this is a valid download.
- GURL referrer = controller_->GetActiveEntry()->url();
- if (flags_ & FLAG_INLINE_INSTALL)
- referrer = GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + id_);
-
- // The download url for the given extension is contained in |download_url_|.
- // We will navigate the current tab to this url to start the download. The
- // download system will then pass the crx to the CrxInstaller.
- controller_->LoadURL(download_url_,
- referrer,
- content::PAGE_TRANSITION_LINK,
- std::string());
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&GetDownloadFilePath, id_,
+ base::Bind(&WebstoreInstaller::StartDownload,
+ base::Unretained(this))));
}
@@ -128,6 +149,28 @@ void WebstoreInstaller::Observe(int type,
}
}
+void WebstoreInstaller::StartDownload(FilePath file) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // TODO(mihaip): For inline installs, we pretend like the referrer is the
+ // gallery, even though this could be an inline install, in order to pass the
+ // checks in ExtensionService::IsDownloadFromGallery. We should instead pass
+ // the real referrer, track if this is an inline install in the whitelist
+ // entry and look that up when checking that this is a valid download.
+ GURL referrer = controller_->GetActiveEntry()->url();
+ if (flags_ & FLAG_INLINE_INSTALL)
+ referrer = GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + id_);
+
+ DownloadSaveInfo save_info;
+ save_info.file_path = file;
+
+ // The download url for the given extension is contained in |download_url_|.
+ // We will navigate the current tab to this url to start the download. The
+ // download system will then pass the crx to the CrxInstaller.
+ profile_->GetDownloadManager()->DownloadUrlToFile(
+ download_url_, referrer, "", save_info, controller_->tab_contents());
+}
+
void WebstoreInstaller::ReportFailure(const std::string& error) {
if (delegate_)
delegate_->OnExtensionInstallFailure(id_, error);
diff --git a/chrome/browser/extensions/webstore_installer.h b/chrome/browser/extensions/webstore_installer.h
index c804355..ef2bbbc 100644
--- a/chrome/browser/extensions/webstore_installer.h
+++ b/chrome/browser/extensions/webstore_installer.h
@@ -15,6 +15,7 @@
#include "content/public/browser/notification_registrar.h"
#include "googleurl/src/gurl.h"
+class FilePath;
class NavigationController;
class Profile;
@@ -60,6 +61,9 @@ class WebstoreInstaller : public content::NotificationObserver,
const content::NotificationDetails& details) OVERRIDE;
private:
+ // Starts downloading the extension to |file_path|.
+ void StartDownload(FilePath file_path);
+
// Reports an install |error| to the delegate for the given extension if this
// managed its installation. This also removes the associated PendingInstall.
void ReportFailure(const std::string& error);
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index ca91aee..52dfbf4 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -75,6 +75,10 @@ const char kAppsGalleryReturnTokens[] = "apps-gallery-return-tokens";
// The URL to use for the gallery link in the app launcher.
const char kAppsGalleryURL[] = "apps-gallery-url";
+// The URL that the webstore APIs download extensions from.
+// Note: the URL must contain one '%s' for the extension ID.
+const char kAppsGalleryDownloadURL[] = "apps-gallery-download-url";
+
// The update url used by gallery/webstore extensions.
const char kAppsGalleryUpdateURL[] = "apps-gallery-update-url";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 501cb4c..5bb54c1 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -38,6 +38,7 @@ extern const char kAppNotifyChannelServerURL[];
extern const char kAppsCheckoutURL[];
extern const char kAppsGalleryReturnTokens[];
extern const char kAppsGalleryURL[];
+extern const char kAppsGalleryDownloadURL[];
extern const char kAppsGalleryUpdateURL[];
extern const char kAppsNewInstallBubble[];
extern const char kAppsNoThrob[];
diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json
index 7e25c33..df1a44d 100644
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -7904,7 +7904,7 @@
{
"name": "expected_id",
"type": "string",
- "description": "The id of the extension to be installled. This should match a previous call to beginInstallWithManifest3."
+ "description": "The id of the extension to be installed. This should match a previous call to beginInstallWithManifest3."
},
{
"name": "callback",
@@ -7915,6 +7915,36 @@
]
},
{
+ "name": "silentlyInstall",
+ "description": "Silently installs the specified extension, which must already be whitelisted in Chrome.",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "The id of the extension to be installled.",
+ "minLength": 32,
+ "maxLength": 32
+ },
+ "manifest": {
+ "type": "string",
+ "description": "A string with the contents of the extension's manifest.json file. During the install process, the browser will check that the downloaded extension's manifest matches what was passed in here.",
+ "minLength": 1
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "description": "Called when the extension installation has completed. chrome.extension.lastError may be set if the extension install failed.",
+ "optional": "true",
+ "parameters": []
+ }
+ ]
+ },
+ {
"name": "getBrowserLogin",
"description": "Returns the logged-in sync user login if there is one, or the empty string otherwise.",
"parameters": [
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg.pem b/chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg.pem
new file mode 100644
index 0000000..40fd445
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAK2fNXqRvU7cAJmPp
+XYmV0x96j9157th9YOPgP2qWtNEYp+e4pDgtr6FbmnrytDTbCCkyy2GgJmhX7I1x9
+FPZv7/kAYEH78IDNMHyEtbHJOkAhofZdVT7KdtDehhcZziKdBjtML/gY33ZGMBmDM
+PZsGJART/Zt9lj/5jexv80UiVAgMBAAECgYEAqe/VFl0jn9YyOBujZhttAw+Tgf3T
+jVL6TYTeSmq9b1/V8EpBlHB+LcjII6CI6RCnGuRxgePOZ5DN6848ACDpuzyA7m1/x
+ndm0KZnQN43EpMj8TLHcsnIZOiO2P/3Rc/srubwZR2oqgZicC8+mYNvwN1d+FHt6u
+2HzivMIcOJK0ECQQDWb0buj6XyV2dhdvhOwlONQYHPKWutNjPfDBf0o+rRGCW4SA9
+l9dozvkVnTuuGnv7PQBRDIae1zOKx8uHBz6zdAkEAz0a2S2hAxgeBSpt2UmixuYwA
+aLi3AX4tT6eAgAXGymVwtRXcBPUijB4itE3DHcFhyk4IzjBFFxdryMxAArkTGQJAL
+h/3gP8zMeKtISDnHoHcWQkbNAVz3OlI1RST3pKXwuxPyMjvTv5INlMaOLOYI/f1VX
+0yHpKRsaBlruNQvlC+nQJBAMxOcOUHD8GiKCi37/ruwy+W9dhDKe/IxTTcb+bAyt8
+4+c0kjMg+MB1YnvCGLaqosJFONZO3NIK8TTuRSA7nChECQQCNMCKYF/3NftJOFZKP
+eYGKagaFjwuPHj39qpGKJtCdwg5oIAtw/xlz7rSRfegQ/hGTAikXiPHt0ZIjU4Tps
+v+k
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg/manifest.json b/chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg/manifest.json
new file mode 100644
index 0000000..7ffc6e3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/begfmnajjkbjdgmffnjaojchoncnmngg/manifest.json
@@ -0,0 +1,8 @@
+{
+ "name": "Bundle App 2",
+ "version": "1",
+ "app": {
+ "urls": [ "http://www.testapp2.com" ],
+ "launch": { "web_url": "http://www.testapp2.com" }
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk.pem b/chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk.pem
new file mode 100644
index 0000000..f69f496
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMRkvle1bZcmaeGv1
+/Ia8WRIosC5dr7E70gP3XOfwE+70EnC+Ytj3aU99VBbFow2tTgC2uAY+0P5HXvH9K
+woeJdPtOOPdRZ+VvE9JqwsEKps+C915YPb7iYIIoU3IXEe3O3ox8F4bPu1iiNabtr
+n6lopCH7E22QeSR5hPdVO8fq5AgMBAAECgYEAn0I23HdClHTRHfPzwN+6aqFAYdrE
+AXU/uQcshKvCTqY2BOq4ZCGqxmoU+YG0KiXfFLmz9lAryfZEw3Dl54m2J0oKHLgpX
+Fh2NsNPBAFTIgz8LLqXZxQ/30luxPLipTOX4mcN0ULX0MkQsxf/TgS0PrCPfgzFBU
+SKDETsBO7g/dUCQQDjE2zhcxMrmuqpF1Cy0G28+IzBjI0YooM2ZHiaSh/r/yO8ywN
+DLS3TTD0RggHJiqRCZnlrYgzDeqPW1aPv2tSXAkEA3WjRFEhAjtB5Ct4npVIyqGzW
+q+hQncG48T7PmOXdN+jL72XafEkKTJ0N0wghr9YwFda2EyBD0HD1FKnW+xIFLwJAW
+p3A4IMUjl0m8c1tFb6ZXETvnrlhAQixRf54JlIYRQwvDcMSDTe1RtHwuNDht7TM8f
+aE07ZwE34Ybb4ZyrjQBwJAfW7MRDlKmZ3xdP62ZypSGKjQVUOfqD//jmyPH4fZ87q
+nDlEdnhujAhRXqJ6KtxsY0sZ5EAzPXl8f+Tze1g43cQJAQa/Ycwm+IuaSkYeOM7oJ
+by7PRPZ7CqQknxBLfGgCnfmhEIcfNaa1+o+Fbf8GZNlQNzkxmq3zD9TDEn5zCdRa/
+w==
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk/manifest.json b/chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk/manifest.json
new file mode 100644
index 0000000..78c6abe
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/bmfoocgfinpmkmlbjhcbofejhkhlbchk/manifest.json
@@ -0,0 +1,5 @@
+{
+ "name": "Extension Bundle 1",
+ "version": "1",
+ "permissions": [ "tabs" ]
+}
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe.pem b/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe.pem
new file mode 100644
index 0000000..045205b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOwcURDS6XmYMM8i0
+e46qYuH8OZImKV+FClIHYH6gVtsqRWGEFsGstkQa7PYP2eZRe1Y6C/bbrzAbPtNEZ
+8HDjtdDDLaO5buNyGHSP0xevj1QexwFYSuE0Skwatcz7vK32EkUzu1fuHeX3GuzOD
+tIk+drHNXcsIPggZ/U7X1DffrAgMBAAECgYEAqFGHePbiekyEfyXTgoFPXKkMkx4t
+s8ytksBWSNLMZOCRqUZpYnrkBNov7YW7rZ8Wup0m6PcFeomzJ3NJnJTrDwf8kDlRN
+GvVyfm2WYtuBfNEdielS1IGDdG5oXryFYkbQtstVvMEPRCmPylfaQEh7mA9EBoDJO
+KmHBC/vl4fzAECQQD/YF9SjhKTeP6t+eQ5nNhxKz4YlAo/ute6MoMAj335RjsLBAa
+S7QdAKoS30IRmJA2U9/q0grO9XOkL1hLEfD4lAkEA7K/m34Q8evMlN/372gSbbIkg
+xiv7h9mnWa99o673tUasHOchUcNjRUo7FPooGNF17GmCrg3exXXHlVrWOd9YzwJBA
+JmgXxepshEXS5Zbaukhqq9BxURB4nx+KQKxGk+/AphvoFs7G71Na/w018xAWzWa4L
+TKDP6EVh5Hg0aEjJu45iUCQDaJjZxBPyJhdlkBiA/DcgC/VDL1nX6/E0WiH0QhI+i
+8QRpj05SgffZQVW7O+YBGe3KfGUJ75bIAIp3ykVxCb5cCQHdBAeoCpV2SgRN3vwf7
+aJmjRmF1j3rBXIgpAZQ77XU30ZPwIy9DoaV1JPF/2fqmwjhBM8Ug8IlNusOkG31Op
+lw=
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/content_script.js b/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/content_script.js
new file mode 100644
index 0000000..1ea18e2
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/content_script.js
@@ -0,0 +1,6 @@
+// Copyright (c) 2011 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 x = 0;
+x + 1;
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/manifest.json b/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/manifest.json
new file mode 100644
index 0000000..784cd53
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/mpneghmdnmaolkljkipbhaienajcflfe/manifest.json
@@ -0,0 +1,10 @@
+{
+ "name": "Extension Bundle 2",
+ "version": "1",
+ "permissions": ["management", "http://google.com" ],
+ "content_script": [{
+ "matches": [ "http://www.example.com/*" ],
+ "js": [ "content_script.js" ],
+ "run_at": "document_start"
+ }]
+}
diff --git a/chrome/test/data/extensions/api_test/webstore_private/common.js b/chrome/test/data/extensions/api_test/webstore_private/common.js
index 358ced0..17025a4 100644
--- a/chrome/test/data/extensions/api_test/webstore_private/common.js
+++ b/chrome/test/data/extensions/api_test/webstore_private/common.js
@@ -10,22 +10,29 @@ var appId = "iladmdjkfniedhfhcfoefgojhgaiaccc";
var assertEq = chrome.test.assertEq;
var assertNoLastError = chrome.test.assertNoLastError;
+var assertTrue = chrome.test.assertTrue;
var callbackFail = chrome.test.callbackFail;
var callbackPass = chrome.test.callbackPass;
var listenOnce = chrome.test.listenOnce;
var runTests = chrome.test.runTests;
var succeed = chrome.test.succeed;
-// Calls |callback| with true/false indicating whether an item with an id of
-// extensionId is installed.
-function checkInstalled(callback) {
+// Calls |callback| with true/false indicating whether an item with the |id|
+// is installed.
+function checkItemInstalled(id, callback) {
chrome.management.getAll(function(extensions) {
callback(extensions.some(function(ext) {
- return ext.id == extensionId;
+ return ext.id == id;
}));
});
}
+// Calls |callback| with true/false indicating whether an item with an id of
+// extensionId is installed.
+function checkInstalled(callback) {
+ checkItemInstalled(extensionId, callback);
+}
+
var cachedIcon = null;
var img = null;
diff --git a/chrome/test/data/extensions/api_test/webstore_private/silently_install.html b/chrome/test/data/extensions/api_test/webstore_private/silently_install.html
new file mode 100644
index 0000000..49928cd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_private/silently_install.html
@@ -0,0 +1,71 @@
+<script src="common.js"></script>
+<script>
+
+var extension1 = {
+ 'id': 'bmfoocgfinpmkmlbjhcbofejhkhlbchk',
+ 'manifest':
+ '{\
+ "name": "Extension Bundle 1",\
+ "version": "1",\
+ "permissions": [ "tabs" ]\
+ }'
+};
+
+var extension2 = {
+ 'id': 'mpneghmdnmaolkljkipbhaienajcflfe',
+ 'manifest':
+ '{\
+ "name": "Extension Bundle 2",\
+ "version": "1",\
+ "permissions": ["management", "http://google.com" ],\
+ "content_script": [{\
+ "matches": [ "http://www.example.com/*" ],\
+ "js": [ "content_script.js" ],\
+ "run_at": "document_start"\
+ }]\
+ }'
+};
+
+var extension3 = {
+ 'id': 'begfmnajjkbjdgmffnjaojchoncnmngg',
+ 'manifest':
+ '{\
+ "name": "Bundle App 2",\
+ "version": "1",\
+ "app": {\
+ "urls": [ "http://www.testapp2.com" ],\
+ "launch": { "web_url": "http://www.testapp2.com" }\
+ }\
+ }'
+};
+
+runTests([
+ function invalidID() {
+ var expectedError = "Invalid id";
+ chrome.webstorePrivate.silentlyInstall(
+ { 'id': 'dladmdjkfniedhfhcfoefgojhgaiaccc', 'manifest': getManifest() },
+ callbackFail(expectedError));
+ },
+
+ function successfulInstall() {
+ chrome.webstorePrivate.silentlyInstall(extension1, callbackPass(function() {
+ checkItemInstalled(
+ extension1.id,
+ callbackPass(function(result) { assertTrue(result); }));
+ }));
+
+ chrome.webstorePrivate.silentlyInstall(extension2, callbackPass(function() {
+ checkItemInstalled(
+ extension2.id,
+ callbackPass(function(result) { assertTrue(result); }));
+ chrome.webstorePrivate.silentlyInstall(
+ extension3, callbackPass(function() {
+ checkItemInstalled(
+ extension3.id,
+ callbackPass(function(result) { assertTrue(result); }));
+ }));
+ }));
+ }
+]);
+
+</script>