diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-02 23:53:46 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-02 23:53:46 +0000 |
commit | f66a50a51caad37a29fd51eb26b14d96cc9f51eb (patch) | |
tree | 929cacefa5c3ca6a70c308f8f8859e5fdf82ea2f /chrome | |
parent | 47d2f41e5f17f1daab75668b742204d61fba014a (diff) | |
download | chromium_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')
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> |