Chromium Code Reviews| Index: chrome/browser/extensions/webstore_inline_installer.cc |
| diff --git a/chrome/browser/extensions/webstore_inline_installer.cc b/chrome/browser/extensions/webstore_inline_installer.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..bbe0a70cc3976eccf4fc570e7a86760c95f21584 |
| --- /dev/null |
| +++ b/chrome/browser/extensions/webstore_inline_installer.cc |
| @@ -0,0 +1,322 @@ |
| +// 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. |
| + |
| +#include "chrome/browser/extensions/webstore_inline_installer.h" |
| + |
| +#include <vector> |
| + |
| +#include "base/string_util.h" |
| +#include "base/values.h" |
| +#include "chrome/browser/browser_process.h" |
| +#include "chrome/browser/extensions/crx_installer.h" |
| +#include "chrome/browser/extensions/extension_install_dialog.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "chrome/common/chrome_utility_messages.h" |
| +#include "chrome/common/extensions/extension.h" |
| +#include "chrome/common/extensions/extension_constants.h" |
| +#include "content/browser/tab_contents/tab_contents.h" |
| +#include "content/browser/utility_process_host.h" |
| +#include "googleurl/src/gurl.h" |
| +#include "net/base/escape.h" |
| +#include "net/url_request/url_request_status.h" |
| + |
| +const char kManifestKey[] = "manifest"; |
| +const char kIconUrlKey[] = "icon_url"; |
| +const char kLocalizedNameKey[] = "localized_name"; |
| + |
| +const char kWebstoreRequestError[] = "Could not fetch data from webstore"; |
| +const char kInvalidWebstoreResponseError[] = "Invalid webstore reponse"; |
| +const char kInvalidManifestError[] = "Invalid manifest"; |
| +const char kUserCancelledError[] = "User cancelled install"; |
| + |
| +// A flag used for WebstoreInlineInstaller::SetAutoConfirmForTests. |
| +enum AutoConfirmForTest { |
| + DO_NOT_SKIP = 0, |
| + PROCEED, |
| + ABORT |
| +}; |
| +AutoConfirmForTest auto_confirm_for_tests = DO_NOT_SKIP; |
| + |
| +class SafeWebstoreResponseParser : public UtilityProcessHost::Client { |
| + public: |
| + SafeWebstoreResponseParser(WebstoreInlineInstaller *client, |
| + const std::string& webstore_data) |
| + : client_(client) |
| + , webstore_data_(webstore_data) |
|
asargent_no_longer_on_chrome
2011/08/26 17:50:40
commas in weird place here and line below
Mihai Parparita -not on Chrome
2011/08/29 19:00:56
Fixed.
|
| + , utility_host_(NULL) {} |
| + |
| + void Start() { |
| + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + BrowserThread::PostTask( |
| + BrowserThread::IO, |
| + FROM_HERE, |
| + NewRunnableMethod(this, |
| + &SafeWebstoreResponseParser::StartWorkOnIOThread)); |
| + } |
| + |
| + void StartWorkOnIOThread() { |
| + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + utility_host_ = new UtilityProcessHost(this, BrowserThread::IO); |
| + utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_)); |
| + } |
| + |
| + // Implementing pieces of the UtilityProcessHost::Client interface. |
| + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { |
| + bool handled = true; |
| + IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message) |
| + IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded, |
| + OnJSONParseSucceeded) |
| + IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed, |
| + OnJSONParseFailed) |
| + IPC_MESSAGE_UNHANDLED(handled = false) |
| + IPC_END_MESSAGE_MAP() |
| + return handled; |
| + } |
| + |
| + void OnJSONParseSucceeded(const ListValue& wrapper) { |
| + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + Value* value = NULL; |
| + CHECK(wrapper.Get(0, &value)); |
| + if (value->IsType(Value::TYPE_DICTIONARY)) { |
| + parsed_webstore_data_.reset( |
| + static_cast<DictionaryValue*>(value)->DeepCopy()); |
| + } else { |
| + error_ = kInvalidWebstoreResponseError; |
| + } |
| + |
| + ReportResults(); |
| + } |
| + |
| + virtual void OnJSONParseFailed(const std::string& error_message) { |
| + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + error_ = error_message; |
| + ReportResults(); |
| + } |
| + |
| + void ReportResults() { |
| + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + |
| + // The utility_host_ will take care of deleting itself after this call. |
| + utility_host_ = NULL; |
| + |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, |
| + FROM_HERE, |
| + NewRunnableMethod(this, |
| + &SafeWebstoreResponseParser::ReportResultOnUIThread)); |
| + } |
| + |
| + void ReportResultOnUIThread() { |
| + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + if (error_.empty() && parsed_webstore_data_.get()) { |
| + client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release()); |
| + } else { |
| + client_->OnWebstoreResponseParseFailure(error_); |
| + } |
| + } |
| + |
| + private: |
| + virtual ~SafeWebstoreResponseParser() {} |
| + |
| + WebstoreInlineInstaller* client_; |
| + |
| + std::string webstore_data_; |
| + |
| + UtilityProcessHost* utility_host_; |
| + |
| + std::string error_; |
| + scoped_ptr<DictionaryValue> parsed_webstore_data_; |
| +}; |
| + |
| +WebstoreInlineInstaller::WebstoreInlineInstaller(TabContents* tab_contents, |
| + std::string webstore_item_id, |
| + Delegate* delegate) |
| + : tab_contents_(tab_contents) |
| + , id_(webstore_item_id) |
|
asargent_no_longer_on_chrome
2011/08/26 17:50:40
commas in weird place here too
Mihai Parparita -not on Chrome
2011/08/29 19:00:56
Fixed.
|
| + , delegate_(delegate) {} |
| + |
| +WebstoreInlineInstaller::~WebstoreInlineInstaller() { |
| +} |
| + |
| +void WebstoreInlineInstaller::BeginInstall() { |
| + GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_)); |
|
asargent_no_longer_on_chrome
2011/08/26 17:50:40
It might be good to add a check of Extension::IdIs
Mihai Parparita -not on Chrome
2011/08/29 19:00:56
Done.
|
| + |
| + AddRef(); // Balanced in CompleteInstall. |
| + |
| + webstore_data_url_fetcher_.reset( |
| + new URLFetcher(webstore_data_url, URLFetcher::GET, this)); |
| + Profile* profile = Profile::FromBrowserContext( |
| + tab_contents_->browser_context()); |
| + webstore_data_url_fetcher_->set_request_context( |
| + profile->GetRequestContext()); |
| + webstore_data_url_fetcher_->Start(); |
| +} |
| + |
| +void WebstoreInlineInstaller::SetAutoConfirmForTests( |
|
asargent_no_longer_on_chrome
2011/08/26 17:50:40
add comment with "// static" here
Mihai Parparita -not on Chrome
2011/08/29 19:00:56
Now that I've refactored the auto-confirm code to
|
| + bool should_proceed) { |
| + auto_confirm_for_tests = should_proceed ? PROCEED : ABORT; |
| +} |
| + |
| +void WebstoreInlineInstaller::OnURLFetchComplete(const URLFetcher* source) { |
| + CHECK_EQ(webstore_data_url_fetcher_.get(), source); |
| + |
| + if (!webstore_data_url_fetcher_->status().is_success() || |
| + webstore_data_url_fetcher_->response_code() != 200) { |
| + CompleteInstall(kWebstoreRequestError); |
| + return; |
| + } |
| + |
| + std::string webstore_json_data; |
| + webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data); |
| + webstore_data_url_fetcher_.reset(); |
| + |
| + scoped_refptr<SafeWebstoreResponseParser> parser = |
| + new SafeWebstoreResponseParser(this, webstore_json_data); |
| + // The parser will call us back via OnWebstoreResponseParseSucces or |
| + // OnWebstoreResponseParseFailure. |
| + parser->Start(); |
| +} |
| + |
| +void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess( |
| + DictionaryValue* webstore_data) { |
| + webstore_data_.reset(webstore_data); |
| + |
| + std::string manifest; |
| + if (!webstore_data->GetString(kManifestKey, &manifest)) { |
| + CompleteInstall(kInvalidWebstoreResponseError); |
| + return; |
| + } |
| + |
| + // Localized name is optional. |
| + if (webstore_data->HasKey(kLocalizedNameKey) && |
| + !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) { |
| + CompleteInstall(kInvalidWebstoreResponseError); |
| + return; |
| + } |
| + |
| + // Icon URL is optional. |
| + GURL icon_url; |
| + if (webstore_data->HasKey(kIconUrlKey)) { |
| + std::string icon_url_string; |
| + if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) { |
| + CompleteInstall(kInvalidWebstoreResponseError); |
| + return; |
| + } |
| + icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve( |
| + icon_url_string); |
| + if (!icon_url.is_valid()) { |
| + CompleteInstall(kInvalidWebstoreResponseError); |
| + return; |
| + } |
| + } |
| + |
| + scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper( |
| + this, |
| + manifest, |
| + "", // We don't have any icon data. |
| + icon_url, |
| + Profile::FromBrowserContext(tab_contents_->browser_context())-> |
| + GetRequestContext()); |
| + // The helper will call us back via OnWebstoreParseSucces or |
| + // OnWebstoreParseFailure. |
| + helper->Start(); |
| +} |
| + |
| +void WebstoreInlineInstaller::OnWebstoreResponseParseFailure( |
| + const std::string& error) { |
| + CompleteInstall(error); |
| +} |
| + |
| +void WebstoreInlineInstaller::OnWebstoreParseSuccess( |
| + const SkBitmap& icon, |
| + base::DictionaryValue* manifest) { |
| + manifest_.reset(manifest); |
| + icon_ = icon; |
| + |
| + scoped_ptr<DictionaryValue> localized_manifest; |
| + if (!localized_name_.empty()) { |
| + localized_manifest.reset(manifest->DeepCopy()); |
| + localized_manifest->SetString(extension_manifest_keys::kName, |
| + localized_name_); |
| + } |
| + |
| + std::string init_errors; |
| + scoped_refptr<Extension> dummy_extension = Extension::CreateWithId( |
| + FilePath(), |
| + Extension::INTERNAL, |
| + localized_manifest.get() ? *localized_manifest.get() : *manifest, |
| + Extension::NO_FLAGS, |
| + id_, |
| + &init_errors); |
| + if (!dummy_extension.get()) { |
| + CompleteInstall(kInvalidManifestError); |
| + return; |
| + } |
| + |
| + if (icon_.empty()) |
| + icon_ = Extension::GetDefaultIcon(dummy_extension->is_app()); |
| + |
| + // In tests, we may have setup to proceed or abort without putting up the real |
| + // confirmation dialog. |
| + if (auto_confirm_for_tests != DO_NOT_SKIP) { |
| + if (auto_confirm_for_tests == PROCEED) |
| + this->InstallUIProceed(); |
| + else |
| + this->InstallUIAbort(true); |
| + return; |
| + } |
| + |
| + Profile* profile = Profile::FromBrowserContext( |
| + tab_contents_->browser_context()); |
| + ShowExtensionInstallDialog(profile, |
| + this, |
| + dummy_extension.get(), |
| + &icon_, |
| + dummy_extension->GetPermissionMessageStrings(), |
| + ExtensionInstallUI::INSTALL_PROMPT); |
|
asargent_no_longer_on_chrome
2011/08/26 17:50:40
Do you think it's worth it to extract this code to
Mihai Parparita -not on Chrome
2011/08/29 19:00:56
Added ShowExtensionInstallDialogForManifest (the s
|
| +} |
| + |
| +void WebstoreInlineInstaller::OnWebstoreParseFailure( |
| + InstallHelperResultCode result_code, |
| + const std::string& error_message) { |
| + CompleteInstall(error_message); |
| +} |
| + |
| +void WebstoreInlineInstaller::InstallUIProceed() { |
| + CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry; |
| + |
| + entry->parsed_manifest.reset(manifest_.get()->DeepCopy()); |
| + entry->localized_name = localized_name_; |
| + entry->use_app_installed_bubble = true; |
| + CrxInstaller::SetWhitelistEntry(id_, entry); |
| + |
| + GURL install_url(extension_urls::GetWebstoreInstallUrl( |
| + id_, g_browser_process->GetApplicationLocale())); |
| + |
| + NavigationController& controller = tab_contents_->controller(); |
| + // TODO(mihaip): we pretend like the referrer is the gallery in order to pass |
| + // the checks in ExtensionService::IsDownloadFromGallery. We should instead |
| + // pass the real referrer, track that this is an inline install in the |
| + // whitelist entry and look that up when checking that this is a valid |
| + // download. |
| + GURL referrer(extension_urls::GetWebstoreItemDetailURLPrefix() + id_); |
| + controller.LoadURL(install_url, referrer, PageTransition::LINK); |
| + |
| + // TODO(mihaip): the success message should happen later, when the extension |
| + // is actually downloaded and installed. |
|
asargent_no_longer_on_chrome
2011/08/26 17:50:40
Yeah, for the case where the download succeeds you
|
| + CompleteInstall(""); |
| +} |
| + |
| +void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) { |
| + CompleteInstall(kUserCancelledError); |
| +} |
| + |
| +void WebstoreInlineInstaller::CompleteInstall(const std::string& error) { |
| + if (error.empty()) { |
| + delegate_->OnInlineInstallSuccess(); |
| + } else { |
| + delegate_->OnInlineInstallFailure(error); |
| + } |
| + Release(); // Matches the AddRef in BeginInstall. |
| +} |