Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/extensions/webstore_inline_installer.h" | |
| 6 | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/string_util.h" | |
| 10 #include "base/values.h" | |
| 11 #include "chrome/browser/browser_process.h" | |
| 12 #include "chrome/browser/extensions/crx_installer.h" | |
| 13 #include "chrome/browser/extensions/extension_install_dialog.h" | |
| 14 #include "chrome/browser/profiles/profile.h" | |
| 15 #include "chrome/common/chrome_utility_messages.h" | |
| 16 #include "chrome/common/extensions/extension.h" | |
| 17 #include "chrome/common/extensions/extension_constants.h" | |
| 18 #include "content/browser/tab_contents/tab_contents.h" | |
| 19 #include "content/browser/utility_process_host.h" | |
| 20 #include "googleurl/src/gurl.h" | |
| 21 #include "net/base/escape.h" | |
| 22 #include "net/url_request/url_request_status.h" | |
| 23 | |
| 24 const char kManifestKey[] = "manifest"; | |
| 25 const char kIconUrlKey[] = "icon_url"; | |
| 26 const char kLocalizedNameKey[] = "localized_name"; | |
| 27 | |
| 28 const char kWebstoreRequestError[] = "Could not fetch data from webstore"; | |
| 29 const char kInvalidWebstoreResponseError[] = "Invalid webstore reponse"; | |
| 30 const char kInvalidManifestError[] = "Invalid manifest"; | |
| 31 const char kUserCancelledError[] = "User cancelled install"; | |
| 32 | |
| 33 // A flag used for WebstoreInlineInstaller::SetAutoConfirmForTests. | |
| 34 enum AutoConfirmForTest { | |
| 35 DO_NOT_SKIP = 0, | |
| 36 PROCEED, | |
| 37 ABORT | |
| 38 }; | |
| 39 AutoConfirmForTest auto_confirm_for_tests = DO_NOT_SKIP; | |
| 40 | |
| 41 class SafeWebstoreResponseParser : public UtilityProcessHost::Client { | |
| 42 public: | |
| 43 SafeWebstoreResponseParser(WebstoreInlineInstaller *client, | |
| 44 const std::string& webstore_data) | |
| 45 : client_(client) | |
| 46 , 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.
| |
| 47 , utility_host_(NULL) {} | |
| 48 | |
| 49 void Start() { | |
| 50 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 51 BrowserThread::PostTask( | |
| 52 BrowserThread::IO, | |
| 53 FROM_HERE, | |
| 54 NewRunnableMethod(this, | |
| 55 &SafeWebstoreResponseParser::StartWorkOnIOThread)); | |
| 56 } | |
| 57 | |
| 58 void StartWorkOnIOThread() { | |
| 59 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 60 utility_host_ = new UtilityProcessHost(this, BrowserThread::IO); | |
| 61 utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_)); | |
| 62 } | |
| 63 | |
| 64 // Implementing pieces of the UtilityProcessHost::Client interface. | |
| 65 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { | |
| 66 bool handled = true; | |
| 67 IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message) | |
| 68 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded, | |
| 69 OnJSONParseSucceeded) | |
| 70 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed, | |
| 71 OnJSONParseFailed) | |
| 72 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 73 IPC_END_MESSAGE_MAP() | |
| 74 return handled; | |
| 75 } | |
| 76 | |
| 77 void OnJSONParseSucceeded(const ListValue& wrapper) { | |
| 78 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 79 Value* value = NULL; | |
| 80 CHECK(wrapper.Get(0, &value)); | |
| 81 if (value->IsType(Value::TYPE_DICTIONARY)) { | |
| 82 parsed_webstore_data_.reset( | |
| 83 static_cast<DictionaryValue*>(value)->DeepCopy()); | |
| 84 } else { | |
| 85 error_ = kInvalidWebstoreResponseError; | |
| 86 } | |
| 87 | |
| 88 ReportResults(); | |
| 89 } | |
| 90 | |
| 91 virtual void OnJSONParseFailed(const std::string& error_message) { | |
| 92 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 93 error_ = error_message; | |
| 94 ReportResults(); | |
| 95 } | |
| 96 | |
| 97 void ReportResults() { | |
| 98 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 99 | |
| 100 // The utility_host_ will take care of deleting itself after this call. | |
| 101 utility_host_ = NULL; | |
| 102 | |
| 103 BrowserThread::PostTask( | |
| 104 BrowserThread::UI, | |
| 105 FROM_HERE, | |
| 106 NewRunnableMethod(this, | |
| 107 &SafeWebstoreResponseParser::ReportResultOnUIThread)); | |
| 108 } | |
| 109 | |
| 110 void ReportResultOnUIThread() { | |
| 111 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 112 if (error_.empty() && parsed_webstore_data_.get()) { | |
| 113 client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release()); | |
| 114 } else { | |
| 115 client_->OnWebstoreResponseParseFailure(error_); | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 private: | |
| 120 virtual ~SafeWebstoreResponseParser() {} | |
| 121 | |
| 122 WebstoreInlineInstaller* client_; | |
| 123 | |
| 124 std::string webstore_data_; | |
| 125 | |
| 126 UtilityProcessHost* utility_host_; | |
| 127 | |
| 128 std::string error_; | |
| 129 scoped_ptr<DictionaryValue> parsed_webstore_data_; | |
| 130 }; | |
| 131 | |
| 132 WebstoreInlineInstaller::WebstoreInlineInstaller(TabContents* tab_contents, | |
| 133 std::string webstore_item_id, | |
| 134 Delegate* delegate) | |
| 135 : tab_contents_(tab_contents) | |
| 136 , 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.
| |
| 137 , delegate_(delegate) {} | |
| 138 | |
| 139 WebstoreInlineInstaller::~WebstoreInlineInstaller() { | |
| 140 } | |
| 141 | |
| 142 void WebstoreInlineInstaller::BeginInstall() { | |
| 143 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.
| |
| 144 | |
| 145 AddRef(); // Balanced in CompleteInstall. | |
| 146 | |
| 147 webstore_data_url_fetcher_.reset( | |
| 148 new URLFetcher(webstore_data_url, URLFetcher::GET, this)); | |
| 149 Profile* profile = Profile::FromBrowserContext( | |
| 150 tab_contents_->browser_context()); | |
| 151 webstore_data_url_fetcher_->set_request_context( | |
| 152 profile->GetRequestContext()); | |
| 153 webstore_data_url_fetcher_->Start(); | |
| 154 } | |
| 155 | |
| 156 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
| |
| 157 bool should_proceed) { | |
| 158 auto_confirm_for_tests = should_proceed ? PROCEED : ABORT; | |
| 159 } | |
| 160 | |
| 161 void WebstoreInlineInstaller::OnURLFetchComplete(const URLFetcher* source) { | |
| 162 CHECK_EQ(webstore_data_url_fetcher_.get(), source); | |
| 163 | |
| 164 if (!webstore_data_url_fetcher_->status().is_success() || | |
| 165 webstore_data_url_fetcher_->response_code() != 200) { | |
| 166 CompleteInstall(kWebstoreRequestError); | |
| 167 return; | |
| 168 } | |
| 169 | |
| 170 std::string webstore_json_data; | |
| 171 webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data); | |
| 172 webstore_data_url_fetcher_.reset(); | |
| 173 | |
| 174 scoped_refptr<SafeWebstoreResponseParser> parser = | |
| 175 new SafeWebstoreResponseParser(this, webstore_json_data); | |
| 176 // The parser will call us back via OnWebstoreResponseParseSucces or | |
| 177 // OnWebstoreResponseParseFailure. | |
| 178 parser->Start(); | |
| 179 } | |
| 180 | |
| 181 void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess( | |
| 182 DictionaryValue* webstore_data) { | |
| 183 webstore_data_.reset(webstore_data); | |
| 184 | |
| 185 std::string manifest; | |
| 186 if (!webstore_data->GetString(kManifestKey, &manifest)) { | |
| 187 CompleteInstall(kInvalidWebstoreResponseError); | |
| 188 return; | |
| 189 } | |
| 190 | |
| 191 // Localized name is optional. | |
| 192 if (webstore_data->HasKey(kLocalizedNameKey) && | |
| 193 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) { | |
| 194 CompleteInstall(kInvalidWebstoreResponseError); | |
| 195 return; | |
| 196 } | |
| 197 | |
| 198 // Icon URL is optional. | |
| 199 GURL icon_url; | |
| 200 if (webstore_data->HasKey(kIconUrlKey)) { | |
| 201 std::string icon_url_string; | |
| 202 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) { | |
| 203 CompleteInstall(kInvalidWebstoreResponseError); | |
| 204 return; | |
| 205 } | |
| 206 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve( | |
| 207 icon_url_string); | |
| 208 if (!icon_url.is_valid()) { | |
| 209 CompleteInstall(kInvalidWebstoreResponseError); | |
| 210 return; | |
| 211 } | |
| 212 } | |
| 213 | |
| 214 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper( | |
| 215 this, | |
| 216 manifest, | |
| 217 "", // We don't have any icon data. | |
| 218 icon_url, | |
| 219 Profile::FromBrowserContext(tab_contents_->browser_context())-> | |
| 220 GetRequestContext()); | |
| 221 // The helper will call us back via OnWebstoreParseSucces or | |
| 222 // OnWebstoreParseFailure. | |
| 223 helper->Start(); | |
| 224 } | |
| 225 | |
| 226 void WebstoreInlineInstaller::OnWebstoreResponseParseFailure( | |
| 227 const std::string& error) { | |
| 228 CompleteInstall(error); | |
| 229 } | |
| 230 | |
| 231 void WebstoreInlineInstaller::OnWebstoreParseSuccess( | |
| 232 const SkBitmap& icon, | |
| 233 base::DictionaryValue* manifest) { | |
| 234 manifest_.reset(manifest); | |
| 235 icon_ = icon; | |
| 236 | |
| 237 scoped_ptr<DictionaryValue> localized_manifest; | |
| 238 if (!localized_name_.empty()) { | |
| 239 localized_manifest.reset(manifest->DeepCopy()); | |
| 240 localized_manifest->SetString(extension_manifest_keys::kName, | |
| 241 localized_name_); | |
| 242 } | |
| 243 | |
| 244 std::string init_errors; | |
| 245 scoped_refptr<Extension> dummy_extension = Extension::CreateWithId( | |
| 246 FilePath(), | |
| 247 Extension::INTERNAL, | |
| 248 localized_manifest.get() ? *localized_manifest.get() : *manifest, | |
| 249 Extension::NO_FLAGS, | |
| 250 id_, | |
| 251 &init_errors); | |
| 252 if (!dummy_extension.get()) { | |
| 253 CompleteInstall(kInvalidManifestError); | |
| 254 return; | |
| 255 } | |
| 256 | |
| 257 if (icon_.empty()) | |
| 258 icon_ = Extension::GetDefaultIcon(dummy_extension->is_app()); | |
| 259 | |
| 260 // In tests, we may have setup to proceed or abort without putting up the real | |
| 261 // confirmation dialog. | |
| 262 if (auto_confirm_for_tests != DO_NOT_SKIP) { | |
| 263 if (auto_confirm_for_tests == PROCEED) | |
| 264 this->InstallUIProceed(); | |
| 265 else | |
| 266 this->InstallUIAbort(true); | |
| 267 return; | |
| 268 } | |
| 269 | |
| 270 Profile* profile = Profile::FromBrowserContext( | |
| 271 tab_contents_->browser_context()); | |
| 272 ShowExtensionInstallDialog(profile, | |
| 273 this, | |
| 274 dummy_extension.get(), | |
| 275 &icon_, | |
| 276 dummy_extension->GetPermissionMessageStrings(), | |
| 277 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
| |
| 278 } | |
| 279 | |
| 280 void WebstoreInlineInstaller::OnWebstoreParseFailure( | |
| 281 InstallHelperResultCode result_code, | |
| 282 const std::string& error_message) { | |
| 283 CompleteInstall(error_message); | |
| 284 } | |
| 285 | |
| 286 void WebstoreInlineInstaller::InstallUIProceed() { | |
| 287 CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry; | |
| 288 | |
| 289 entry->parsed_manifest.reset(manifest_.get()->DeepCopy()); | |
| 290 entry->localized_name = localized_name_; | |
| 291 entry->use_app_installed_bubble = true; | |
| 292 CrxInstaller::SetWhitelistEntry(id_, entry); | |
| 293 | |
| 294 GURL install_url(extension_urls::GetWebstoreInstallUrl( | |
| 295 id_, g_browser_process->GetApplicationLocale())); | |
| 296 | |
| 297 NavigationController& controller = tab_contents_->controller(); | |
| 298 // TODO(mihaip): we pretend like the referrer is the gallery in order to pass | |
| 299 // the checks in ExtensionService::IsDownloadFromGallery. We should instead | |
| 300 // pass the real referrer, track that this is an inline install in the | |
| 301 // whitelist entry and look that up when checking that this is a valid | |
| 302 // download. | |
| 303 GURL referrer(extension_urls::GetWebstoreItemDetailURLPrefix() + id_); | |
| 304 controller.LoadURL(install_url, referrer, PageTransition::LINK); | |
| 305 | |
| 306 // TODO(mihaip): the success message should happen later, when the extension | |
| 307 // 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
| |
| 308 CompleteInstall(""); | |
| 309 } | |
| 310 | |
| 311 void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) { | |
| 312 CompleteInstall(kUserCancelledError); | |
| 313 } | |
| 314 | |
| 315 void WebstoreInlineInstaller::CompleteInstall(const std::string& error) { | |
| 316 if (error.empty()) { | |
| 317 delegate_->OnInlineInstallSuccess(); | |
| 318 } else { | |
| 319 delegate_->OnInlineInstallFailure(error); | |
| 320 } | |
| 321 Release(); // Matches the AddRef in BeginInstall. | |
| 322 } | |
| OLD | NEW |