diff options
author | penghuang@chromium.org <penghuang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-17 15:04:58 +0000 |
---|---|---|
committer | penghuang@chromium.org <penghuang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-17 15:04:58 +0000 |
commit | 669b23737667b310f05e9610fce5f635bdbedfc5 (patch) | |
tree | 641aff1069f022ca7074ad49f88ee7d37969510c | |
parent | 14a37807e4ec45423957ee4fd4bc070256b462de (diff) | |
download | chromium_src-669b23737667b310f05e9610fce5f635bdbedfc5.zip chromium_src-669b23737667b310f05e9610fce5f635bdbedfc5.tar.gz chromium_src-669b23737667b310f05e9610fce5f635bdbedfc5.tar.bz2 |
Check and install dependencies in WebstoreInstaller during installing an extension.
This CL improves UI experience of installing an app which depends on other shared modules.
For AppLauncher, the download progress bar will count shared modules.
For NTP, the NTP will not be created until all shared modules are ready.
Uses download shelf for shared modules instead of downloading shared module in background.
BUG=274846
Review URL: https://codereview.chromium.org/25452005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@229129 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/extensions/api/webstore_private/webstore_private_api.cc | 1 | ||||
-rw-r--r-- | chrome/browser/extensions/crx_installer.cc | 53 | ||||
-rw-r--r-- | chrome/browser/extensions/crx_installer.h | 13 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service.cc | 32 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service.h | 9 | ||||
-rw-r--r-- | chrome/browser/extensions/webstore_installer.cc | 179 | ||||
-rw-r--r-- | chrome/browser/extensions/webstore_installer.h | 51 | ||||
-rw-r--r-- | chrome/browser/extensions/webstore_installer_unittest.cc | 3 |
8 files changed, 273 insertions, 68 deletions
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc index ad40866..f01005f 100644 --- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc +++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc @@ -468,6 +468,7 @@ void WebstorePrivateBeginInstallWithManifest3Function::InstallUIProceed() { // If we are enabling the launcher, we should not show the app list in order // to train the user to open it themselves at least once. approval->skip_post_install_ui = params_->details.enable_launcher; + approval->dummy_extension = dummy_extension_; approval->installing_icon = gfx::ImageSkia::CreateFrom1xBitmap(icon_); g_pending_approvals.Get().PushApproval(approval.Pass()); diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc index 2205608..2526a53 100644 --- a/chrome/browser/extensions/crx_installer.cc +++ b/chrome/browser/extensions/crx_installer.cc @@ -108,7 +108,9 @@ CrxInstaller::CrxInstaller( : install_directory_(service_weak->install_directory()), install_source_(Manifest::INTERNAL), approved_(false), - expected_manifest_strict_checking_(true), + expected_manifest_check_level_( + WebstoreInstaller::MANIFEST_CHECK_LEVEL_STRICT), + expected_version_strict_checking_(false), extensions_enabled_(service_weak->extensions_enabled()), delete_source_(false), create_app_shortcut_(false), @@ -143,10 +145,16 @@ CrxInstaller::CrxInstaller( // Mark the extension as approved, but save the expected manifest and ID // so we can check that they match the CRX's. approved_ = true; - expected_manifest_.reset(approval->manifest->DeepCopy()); - expected_manifest_strict_checking_ = approval->strict_manifest_check; + expected_manifest_check_level_ = approval->manifest_check_level; + if (expected_manifest_check_level_ != + WebstoreInstaller::MANIFEST_CHECK_LEVEL_NONE) + expected_manifest_.reset(approval->manifest->DeepCopy()); expected_id_ = approval->extension_id; } + if (approval->minimum_version.get()) { + expected_version_.reset(new Version(*approval->minimum_version)); + expected_version_strict_checking_ = false; + } show_dialog_callback_ = approval->show_dialog_callback; } @@ -247,21 +255,41 @@ CrxInstallerError CrxInstaller::AllowInstall(const Extension* extension) { ASCIIToUTF16(extension->id()))); } - if (expected_version_.get() && - !expected_version_->Equals(*extension->version())) { - return CrxInstallerError( - l10n_util::GetStringFUTF16( - IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION, - ASCIIToUTF16(expected_version_->GetString()), - ASCIIToUTF16(extension->version()->GetString()))); + if (expected_version_.get()) { + if (expected_version_strict_checking_) { + if (!expected_version_->Equals(*extension->version())) { + return CrxInstallerError( + l10n_util::GetStringFUTF16( + IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION, + ASCIIToUTF16(expected_version_->GetString()), + ASCIIToUTF16(extension->version()->GetString()))); + } + } else { + if (extension->version()->CompareTo(*expected_version_) < 0) { + return CrxInstallerError( + l10n_util::GetStringFUTF16( + IDS_EXTENSION_INSTALL_UNEXPECTED_VERSION, + ASCIIToUTF16(expected_version_->GetString() + "+"), + ASCIIToUTF16(extension->version()->GetString()))); + } + } } // Make sure the manifests match if we want to bypass the prompt. if (approved_) { bool valid = false; - if (expected_manifest_.get()) { + if (expected_manifest_check_level_ == + WebstoreInstaller::MANIFEST_CHECK_LEVEL_NONE) { + // To skip manifest checking, the extension must be a shared module + // and not request any permissions. + if (SharedModuleInfo::IsSharedModule(extension) && + PermissionsData::GetActivePermissions(extension)->IsEmpty()) { + valid = true; + } + } else { valid = expected_manifest_->Equals(original_manifest_.get()); - if (!valid && !expected_manifest_strict_checking_) { + if (!valid && expected_manifest_check_level_ == + WebstoreInstaller::MANIFEST_CHECK_LEVEL_LOOSE) { std::string error; scoped_refptr<Extension> dummy_extension = Extension::Create(base::FilePath(), @@ -278,6 +306,7 @@ CrxInstallerError CrxInstaller::AllowInstall(const Extension* extension) { } } } + if (!valid) return CrxInstallerError( l10n_util::GetStringUTF16(IDS_EXTENSION_MANIFEST_INVALID)); diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h index fde15b6..6d55481 100644 --- a/chrome/browser/extensions/crx_installer.h +++ b/chrome/browser/extensions/crx_installer.h @@ -127,6 +127,7 @@ class CrxInstaller void set_expected_version(const Version& val) { expected_version_.reset(new Version(val)); + expected_version_strict_checking_ = true; } bool delete_source() const { return delete_source_; } @@ -288,10 +289,9 @@ class CrxInstaller // extension's manifest must match this for the install to proceed. scoped_ptr<Manifest> expected_manifest_; - // Set to true if we want a strict, exact match check between the actual and - // expected manifest, rather than just a check that the effective permissions - // are the same. - bool expected_manifest_strict_checking_; + // The level of checking when comparing the actual manifest against + // the |expected_manifest_|. + WebstoreInstaller::ManifestCheckLevel expected_manifest_check_level_; // If non-NULL, contains the expected version of the extension we're // installing. Important for external sources, where claiming the wrong @@ -299,6 +299,11 @@ class CrxInstaller // restart. scoped_ptr<Version> expected_version_; + // If true, the actual version should be same with the |expected_version_|, + // Otherwise the actual version should be equal to or newer than + // the |expected_version_|. + bool expected_version_strict_checking_; + // Whether manual extension installation is enabled. We can't just check this // before trying to install because themes are special-cased to always be // allowed. diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 18bcbeb..6a87a5f 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -2384,12 +2384,14 @@ void ExtensionService::UpdateActiveExtensionsInCrashReporter() { crash_keys::SetActiveExtensions(extension_ids); } -ExtensionService::ImportStatus ExtensionService::SatisfyImports( - const Extension* extension) { +ExtensionService::ImportStatus ExtensionService::CheckImports( + const extensions::Extension* extension, + std::list<SharedModuleInfo::ImportInfo>* missing_modules, + std::list<SharedModuleInfo::ImportInfo>* outdated_modules) { + DCHECK(extension); + DCHECK(missing_modules && missing_modules->empty()); + DCHECK(outdated_modules && outdated_modules->empty()); ImportStatus status = IMPORT_STATUS_OK; - std::vector<std::string> pending; - // TODO(elijahtaylor): Message the user if there is a failure that is - // unrecoverable. if (SharedModuleInfo::ImportsModules(extension)) { const std::vector<SharedModuleInfo::ImportInfo>& imports = SharedModuleInfo::GetImports(extension); @@ -2401,7 +2403,7 @@ ExtensionService::ImportStatus ExtensionService::SatisfyImports( if (!imported_module) { if (extension->from_webstore()) { status = IMPORT_STATUS_UNSATISFIED; - pending.push_back(i->extension_id); + missing_modules->push_back(*i); } else { return IMPORT_STATUS_UNRECOVERABLE; } @@ -2410,6 +2412,7 @@ ExtensionService::ImportStatus ExtensionService::SatisfyImports( } else if (version_required.IsValid() && imported_module->version()->CompareTo(version_required) < 0) { if (imported_module->from_webstore()) { + outdated_modules->push_back(*i); status = IMPORT_STATUS_UNSATISFIED; } else { return IMPORT_STATUS_UNRECOVERABLE; @@ -2417,12 +2420,21 @@ ExtensionService::ImportStatus ExtensionService::SatisfyImports( } } } + return status; +} + +ExtensionService::ImportStatus ExtensionService::SatisfyImports( + const Extension* extension) { + std::list<SharedModuleInfo::ImportInfo> noinstalled; + std::list<SharedModuleInfo::ImportInfo> outdated; + ImportStatus status = CheckImports(extension, &noinstalled, &outdated); + if (status == IMPORT_STATUS_UNRECOVERABLE) + return status; if (status == IMPORT_STATUS_UNSATISFIED) { - for (std::vector<std::string>::const_iterator iter = pending.begin(); - iter != pending.end(); - ++iter) { + std::list<SharedModuleInfo::ImportInfo>::const_iterator iter; + for (iter = noinstalled.begin(); iter != noinstalled.end(); ++iter) { pending_extension_manager()->AddFromExtensionImport( - *iter, + iter->extension_id, extension_urls::GetWebstoreUpdateUrl(), IsSharedModule); } diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index ee26e92..d908dc5 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_SERVICE_H_ #define CHROME_BROWSER_EXTENSIONS_EXTENSION_SERVICE_H_ +#include <list> #include <map> #include <set> #include <string> @@ -36,6 +37,7 @@ #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_set.h" +#include "chrome/common/extensions/manifest_handlers/shared_module_info.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" @@ -397,6 +399,13 @@ class ExtensionService IMPORT_STATUS_UNRECOVERABLE }; + // Checks an extension's imports. No installed and outdated imports will be + // stored in |missing_modules| and |outdated_modules|. + ImportStatus CheckImports( + const extensions::Extension* extension, + std::list<extensions::SharedModuleInfo::ImportInfo>* missing_modules, + std::list<extensions::SharedModuleInfo::ImportInfo>* outdated_modules); + // Checks an extension's shared module imports to see if they are satisfied. // If they are not, this function adds the dependencies to the pending install // list if |extension| came from the webstore. diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc index ab1d1c9..5bf00a9 100644 --- a/chrome/browser/extensions/webstore_installer.cc +++ b/chrome/browser/extensions/webstore_installer.cc @@ -4,6 +4,8 @@ #include "chrome/browser/extensions/webstore_installer.h" +#include <vector> + #include "base/basictypes.h" #include "base/bind.h" #include "base/command_line.h" @@ -19,6 +21,7 @@ #include "chrome/browser/download/download_prefs.h" #include "chrome/browser/download/download_stats.h" #include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/install_tracker.h" #include "chrome/browser/extensions/install_tracker_factory.h" #include "chrome/browser/profiles/profile.h" @@ -27,6 +30,7 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/manifest_handlers/shared_module_info.h" #include "chrome/common/omaha_query_params/omaha_query_params.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_manager.h" @@ -68,6 +72,9 @@ const char kInstallCanceledError[] = "Install canceled"; const char kDownloadInterruptedError[] = "Download interrupted"; const char kInvalidDownloadError[] = "Download was not a valid extension or user script"; +const char kDependencyNotFoundError[] = "Dependency not found"; +const char kDependencyNotSharedModuleError[] = + "Dependency is not shared module"; const char kInlineInstallSource[] = "inline"; const char kDefaultInstallSource[] = "ondemand"; const char kAppLauncherInstallSource[] = "applauncher"; @@ -124,7 +131,19 @@ namespace extensions { // static GURL WebstoreInstaller::GetWebstoreInstallURL( - const std::string& extension_id, const std::string& install_source) { + const std::string& extension_id, InstallSource source) { + std::string install_source; + switch (source) { + case INSTALL_SOURCE_INLINE: + install_source = kInlineInstallSource; + break; + case INSTALL_SOURCE_APP_LAUNCHER: + install_source = kAppLauncherInstallSource; + break; + case INSTALL_SOURCE_OTHER: + install_source = kDefaultInstallSource; + } + CommandLine* cmd_line = CommandLine::ForCurrentProcess(); if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) { std::string download_url = @@ -164,7 +183,7 @@ WebstoreInstaller::Approval::Approval() skip_post_install_ui(false), skip_install_dialog(false), enable_launcher(false), - strict_manifest_check(true) { + manifest_check_level(MANIFEST_CHECK_LEVEL_STRICT) { } scoped_ptr<WebstoreInstaller::Approval> @@ -175,6 +194,15 @@ WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) { } scoped_ptr<WebstoreInstaller::Approval> +WebstoreInstaller::Approval::CreateForSharedModule(Profile* profile) { + scoped_ptr<Approval> result(new Approval()); + result->profile = profile; + result->skip_install_dialog = true; + result->manifest_check_level = MANIFEST_CHECK_LEVEL_NONE; + return result.Pass(); +} + +scoped_ptr<WebstoreInstaller::Approval> WebstoreInstaller::Approval::CreateWithNoInstallPrompt( Profile* profile, const std::string& extension_id, @@ -187,7 +215,8 @@ WebstoreInstaller::Approval::CreateWithNoInstallPrompt( new Manifest(Manifest::INVALID_LOCATION, scoped_ptr<DictionaryValue>(parsed_manifest->DeepCopy()))); result->skip_install_dialog = true; - result->strict_manifest_check = strict_manifest_check; + result->manifest_check_level = strict_manifest_check ? + MANIFEST_CHECK_LEVEL_STRICT : MANIFEST_CHECK_LEVEL_LOOSE; return result.Pass(); } @@ -208,25 +237,14 @@ WebstoreInstaller::WebstoreInstaller(Profile* profile, delegate_(delegate), controller_(controller), id_(id), + install_source_(source), download_item_(NULL), - approval_(approval.release()) { + approval_(approval.release()), + total_modules_(0), + download_started_(false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(controller_); - const char* install_source = ""; - switch (source) { - case INSTALL_SOURCE_INLINE: - install_source = kInlineInstallSource; - break; - case INSTALL_SOURCE_APP_LAUNCHER: - install_source = kAppLauncherInstallSource; - break; - case INSTALL_SOURCE_OTHER: - install_source = kDefaultInstallSource; - } - - download_url_ = GetWebstoreInstallURL(id, install_source); - registrar_.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE, content::NotificationService::AllSources()); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, @@ -244,12 +262,30 @@ void WebstoreInstaller::Start() { return; } - base::FilePath download_path = DownloadPrefs::FromDownloadManager( - BrowserContext::GetDownloadManager(profile_))->DownloadPath(); - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, - base::Bind(&GetDownloadFilePath, download_path, id_, - base::Bind(&WebstoreInstaller::StartDownload, this))); + ExtensionService* extension_service = + ExtensionSystem::Get(profile_)->extension_service(); + if (approval_.get() && approval_->dummy_extension) { + ExtensionService::ImportStatus status = + extension_service->CheckImports(approval_->dummy_extension, + &pending_modules_, &pending_modules_); + // For this case, it is because some imports are not shared modules. + if (status == ExtensionService::IMPORT_STATUS_UNRECOVERABLE) { + ReportFailure(kDependencyNotSharedModuleError, + FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE); + return; + } + } + + // Add the extension main module into the list. + SharedModuleInfo::ImportInfo info; + info.extension_id = id_; + pending_modules_.push_back(info); + + total_modules_ = pending_modules_.size(); + + // TODO(crbug.com/305343): Query manifest of dependencises before + // downloading & installing those dependencies. + DownloadNextPendingModule(); std::string name; if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) { @@ -282,8 +318,31 @@ void WebstoreInstaller::Observe(int type, CHECK(profile_->IsSameProfile(content::Source<Profile>(source).ptr())); const Extension* extension = content::Details<const InstalledExtensionInfo>(details)->extension; - if (id_ == extension->id()) + CHECK(!pending_modules_.empty()); + SharedModuleInfo::ImportInfo info = pending_modules_.front(); + pending_modules_.pop_front(); + CHECK_EQ(extension->id(), info.extension_id); + + if (pending_modules_.empty()) { + CHECK_EQ(extension->id(), id_); ReportSuccess(); + } else { + const Version version_required(info.minimum_version); + if (version_required.IsValid() && + extension->version()->CompareTo(version_required) < 0) { + // It should not happen, CrxInstaller will make sure the version is + // equal or newer than version_required. + ReportFailure(kDependencyNotFoundError, + FAILURE_REASON_DEPENDENCY_NOT_FOUND); + } else if (!SharedModuleInfo::IsSharedModule(extension)) { + // It should not happen, CrxInstaller will make sure it is a shared + // module. + ReportFailure(kDependencyNotSharedModuleError, + FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE); + } else { + DownloadNextPendingModule(); + } + } break; } @@ -333,12 +392,33 @@ void WebstoreInstaller::OnDownloadStarted( } DCHECK_EQ(net::OK, error); + DCHECK(!pending_modules_.empty()); download_item_ = item; download_item_->AddObserver(this); - if (approval_) - download_item_->SetUserData(kApprovalKey, approval_.release()); - if (delegate_) - delegate_->OnExtensionDownloadStarted(id_, download_item_); + if (pending_modules_.size() > 1) { + // We are downloading a shared module. We need create an approval for it. + scoped_ptr<Approval> approval = Approval::CreateForSharedModule(profile_); + const SharedModuleInfo::ImportInfo& info = pending_modules_.front(); + approval->extension_id = info.extension_id; + const Version version_required(info.minimum_version); + + if (version_required.IsValid()) { + approval->minimum_version.reset( + new Version(version_required)); + } + download_item_->SetUserData(kApprovalKey, approval.release()); + } else { + // It is for the main module of the extension. We should use the provided + // |approval_|. + if (approval_) + download_item_->SetUserData(kApprovalKey, approval_.release()); + } + + if (!download_started_) { + if (delegate_) + delegate_->OnExtensionDownloadStarted(id_, download_item_); + download_started_ = true; + } } void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) { @@ -355,23 +435,29 @@ void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) { // Wait for other notifications if the download is really an extension. if (!download_crx_util::IsExtensionDownload(*download)) { ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER); - } else { + } else if (pending_modules_.empty()) { + // The download is the last module - the extension main module. if (delegate_) delegate_->OnExtensionDownloadProgress(id_, download); - extensions::InstallTracker* tracker = extensions::InstallTrackerFactory::GetForProfile(profile_); tracker->OnDownloadProgress(id_, 100); } break; case DownloadItem::IN_PROGRESS: { - if (delegate_) + if (delegate_ && pending_modules_.size() == 1) { + // Only report download progress for the main module to |delegrate_|. delegate_->OnExtensionDownloadProgress(id_, download); - - extensions::InstallTracker* tracker = + } + int percent = download->PercentComplete(); + // Only report progress if precent is more than 0 + if (percent >= 0) { + int finished_modules = total_modules_ - pending_modules_.size(); + percent = (percent + finished_modules * 100) / total_modules_; + extensions::InstallTracker* tracker = extensions::InstallTrackerFactory::GetForProfile(profile_); - tracker->OnDownloadProgress(id_, download->PercentComplete()); - + tracker->OnDownloadProgress(id_, percent); + } break; } default: @@ -386,6 +472,27 @@ void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) { download_item_ = NULL; } +void WebstoreInstaller::DownloadNextPendingModule() { + CHECK(!pending_modules_.empty()); + if (pending_modules_.size() == 1) { + DCHECK_EQ(id_, pending_modules_.front().extension_id); + DownloadCrx(id_, install_source_); + } else { + DownloadCrx(pending_modules_.front().extension_id, INSTALL_SOURCE_OTHER); + } +} + +void WebstoreInstaller::DownloadCrx( + const std::string& extension_id, InstallSource source) { + download_url_ = GetWebstoreInstallURL(extension_id, source); + base::FilePath download_path = DownloadPrefs::FromDownloadManager( + BrowserContext::GetDownloadManager(profile_))->DownloadPath(); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&GetDownloadFilePath, download_path, id_, + base::Bind(&WebstoreInstaller::StartDownload, this))); +} + // http://crbug.com/165634 // http://crbug.com/126013 // The current working theory is that one of the many pointers dereferenced in diff --git a/chrome/browser/extensions/webstore_installer.h b/chrome/browser/extensions/webstore_installer.h index 6297881..56458e2 100644 --- a/chrome/browser/extensions/webstore_installer.h +++ b/chrome/browser/extensions/webstore_installer.h @@ -5,15 +5,17 @@ #ifndef CHROME_BROWSER_EXTENSIONS_WEBSTORE_INSTALLER_H_ #define CHROME_BROWSER_EXTENSIONS_WEBSTORE_INSTALLER_H_ +#include <list> #include <string> -#include <vector> #include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/supports_user_data.h" #include "base/values.h" +#include "base/version.h" #include "chrome/browser/extensions/extension_install_prompt.h" +#include "chrome/common/extensions/manifest_handlers/shared_module_info.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_item.h" #include "content/public/browser/notification_observer.h" @@ -34,6 +36,7 @@ class NavigationController; namespace extensions { +class Extension; class Manifest; // Downloads and installs extensions from the web store. @@ -52,9 +55,23 @@ class WebstoreInstaller :public content::NotificationObserver, enum FailureReason { FAILURE_REASON_CANCELLED, + FAILURE_REASON_DEPENDENCY_NOT_FOUND, + FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE, FAILURE_REASON_OTHER }; + enum ManifestCheckLevel { + // Do not check for any manifest equality. + MANIFEST_CHECK_LEVEL_NONE, + + // Only check that the expected and actual permissions have the same + // effective permissions. + MANIFEST_CHECK_LEVEL_LOOSE, + + // All data in the expected and actual manifests must match. + MANIFEST_CHECK_LEVEL_STRICT, + }; + class Delegate { public: virtual void OnExtensionDownloadStarted(const std::string& id, @@ -77,6 +94,9 @@ class WebstoreInstaller :public content::NotificationObserver, struct Approval : public base::SupportsUserData::Data { static scoped_ptr<Approval> CreateWithInstallPrompt(Profile* profile); + // Creates an Approval for installing a shared module. + static scoped_ptr<Approval> CreateForSharedModule(Profile* profile); + // Creates an Approval that will skip putting up an install confirmation // prompt if the actual manifest from the extension to be installed matches // |parsed_manifest|. The |strict_manifest_check| controls whether we want @@ -115,9 +135,9 @@ class WebstoreInstaller :public content::NotificationObserver, // Whether we should enable the launcher before installing the app. bool enable_launcher; - // Whether we want a strict manifest equality check, or just want a match - // for effective permissions. - bool strict_manifest_check; + // Manifest check level for checking actual manifest against expected + // manifest. + ManifestCheckLevel manifest_check_level; // Used to show the install dialog. ExtensionInstallPrompt::ShowDialogCallback show_dialog_callback; @@ -125,6 +145,12 @@ class WebstoreInstaller :public content::NotificationObserver, // The icon to use to display the extension while it is installing. gfx::ImageSkia installing_icon; + // A dummy extension created from |manifest|; + scoped_refptr<Extension> dummy_extension; + + // Required minimum version. + scoped_ptr<Version> minimum_version; + private: Approval(); }; @@ -173,7 +199,7 @@ class WebstoreInstaller :public content::NotificationObserver, // Helper to get install URL. static GURL GetWebstoreInstallURL(const std::string& extension_id, - const std::string& install_source); + InstallSource source); // DownloadManager::DownloadUrl callback. void OnDownloadStarted(content::DownloadItem* item, net::Error error); @@ -182,6 +208,13 @@ class WebstoreInstaller :public content::NotificationObserver, virtual void OnDownloadUpdated(content::DownloadItem* download) OVERRIDE; virtual void OnDownloadDestroyed(content::DownloadItem* download) OVERRIDE; + // Downloads next pending module in |pending_modules_|. + void DownloadNextPendingModule(); + + // Downloads and installs a single Crx with the given |extension_id|. + // This function is used for both the extension Crx and dependences. + void DownloadCrx(const std::string& extension_id, InstallSource source); + // Starts downloading the extension to |file_path|. void StartDownload(const base::FilePath& file_path); @@ -199,11 +232,19 @@ class WebstoreInstaller :public content::NotificationObserver, Delegate* delegate_; content::NavigationController* controller_; std::string id_; + InstallSource install_source_; // The DownloadItem is owned by the DownloadManager and is valid from when // OnDownloadStarted is called (with no error) until OnDownloadDestroyed(). content::DownloadItem* download_item_; scoped_ptr<Approval> approval_; GURL download_url_; + + // Pending modules. + std::list<SharedModuleInfo::ImportInfo> pending_modules_; + // Total extension modules we need download and install (the main module and + // depedences). + int total_modules_; + bool download_started_; }; } // namespace extensions diff --git a/chrome/browser/extensions/webstore_installer_unittest.cc b/chrome/browser/extensions/webstore_installer_unittest.cc index e46dad1..734013a 100644 --- a/chrome/browser/extensions/webstore_installer_unittest.cc +++ b/chrome/browser/extensions/webstore_installer_unittest.cc @@ -24,7 +24,8 @@ bool Contains(const std::string& source, const std::string& target) { TEST(WebstoreInstallerTest, PlatformParams) { std::string id = extensions::id_util::GenerateId("some random string"); std::string source = "inline"; - GURL url = WebstoreInstaller::GetWebstoreInstallURL(id, source); + GURL url = WebstoreInstaller::GetWebstoreInstallURL(id, + WebstoreInstaller::INSTALL_SOURCE_INLINE); std::string query = url.query(); EXPECT_TRUE(Contains(query,StringPrintf("os=%s", OmahaQueryParams::getOS()))); EXPECT_TRUE(Contains(query,StringPrintf("arch=%s", |