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. |
+} |