blob: eb76482f7601f286c78ba00625f610055957a9a5 [file] [log] [blame]
[email protected]bdb74a22012-01-25 20:33:331// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]8915f342011-08-29 22:14:372// 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
[email protected]8e6ac4b2011-10-17 19:04:319#include "base/bind.h"
[email protected]8915f342011-08-29 22:14:3710#include "base/string_util.h"
11#include "base/values.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/extensions/crx_installer.h"
14#include "chrome/browser/extensions/extension_install_dialog.h"
[email protected]655b2b1a2011-10-13 17:13:0615#include "chrome/browser/extensions/extension_service.h"
[email protected]8915f342011-08-29 22:14:3716#include "chrome/browser/profiles/profile.h"
17#include "chrome/common/chrome_utility_messages.h"
18#include "chrome/common/extensions/extension.h"
19#include "chrome/common/extensions/extension_constants.h"
[email protected]a221ef092011-09-07 01:34:1020#include "chrome/common/extensions/url_pattern.h"
[email protected]bdb74a22012-01-25 20:33:3321#include "chrome/common/url_constants.h"
[email protected]ea049a02011-12-25 21:37:0922#include "content/public/browser/web_contents.h"
[email protected]c4f883a2012-02-03 17:02:0723#include "content/public/browser/utility_process_host.h"
24#include "content/public/browser/utility_process_host_client.h"
[email protected]7b419b82011-10-27 04:23:4625#include "content/public/common/url_fetcher.h"
[email protected]8915f342011-08-29 22:14:3726#include "net/base/escape.h"
[email protected]41e9a0de2011-09-14 17:24:5127#include "net/base/load_flags.h"
[email protected]8915f342011-08-29 22:14:3728#include "net/url_request/url_request_status.h"
29
[email protected]631bb742011-11-02 11:29:3930using content::BrowserThread;
[email protected]e5d549d2011-12-28 01:29:2031using content::OpenURLParams;
[email protected]c4f883a2012-02-03 17:02:0732using content::UtilityProcessHost;
33using content::UtilityProcessHostClient;
[email protected]26b5e322011-12-23 01:36:4734using content::WebContents;
[email protected]631bb742011-11-02 11:29:3935
[email protected]8915f342011-08-29 22:14:3736const char kManifestKey[] = "manifest";
37const char kIconUrlKey[] = "icon_url";
38const char kLocalizedNameKey[] = "localized_name";
[email protected]5fdbd6f2011-09-01 17:33:0439const char kLocalizedDescriptionKey[] = "localized_description";
40const char kUsersKey[] = "users";
41const char kAverageRatingKey[] = "average_rating";
42const char kRatingCountKey[] = "rating_count";
[email protected]a221ef092011-09-07 01:34:1043const char kVerifiedSiteKey[] = "verified_site";
[email protected]dd5161a2011-09-14 17:40:1744const char kInlineInstallNotSupportedKey[] = "inline_install_not_supported";
45const char kRedirectUrlKey[] = "redirect_url";
[email protected]8915f342011-08-29 22:14:3746
[email protected]a221ef092011-09-07 01:34:1047const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
48const char kWebstoreRequestError[] =
49 "Could not fetch data from the Chrome Web Store";
50const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
[email protected]8915f342011-08-29 22:14:3751const char kInvalidManifestError[] = "Invalid manifest";
52const char kUserCancelledError[] = "User cancelled install";
[email protected]5da311ea2011-09-14 20:57:3253const char kNoVerifiedSiteError[] =
54 "Inline installs can only be initiated for Chrome Web Store items that "
55 "have a verified site";
56const char kNotFromVerifiedSiteError[] =
[email protected]a221ef092011-09-07 01:34:1057 "Installs can only be initiated by the Chrome Web Store item's verified "
58 "site";
[email protected]dd5161a2011-09-14 17:40:1759const char kInlineInstallSupportedError[] =
60 "Inline installation is not supported for this item. The user will be "
61 "redirected to the Chrome Web Store.";
[email protected]8915f342011-08-29 22:14:3762
[email protected]c4f883a2012-02-03 17:02:0763class SafeWebstoreResponseParser : public UtilityProcessHostClient {
[email protected]8915f342011-08-29 22:14:3764 public:
65 SafeWebstoreResponseParser(WebstoreInlineInstaller *client,
66 const std::string& webstore_data)
67 : client_(client),
[email protected]c4f883a2012-02-03 17:02:0768 webstore_data_(webstore_data) {}
[email protected]8915f342011-08-29 22:14:3769
70 void Start() {
71 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
72 BrowserThread::PostTask(
73 BrowserThread::IO,
74 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:3175 base::Bind(&SafeWebstoreResponseParser::StartWorkOnIOThread, this));
[email protected]8915f342011-08-29 22:14:3776 }
77
78 void StartWorkOnIOThread() {
79 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
[email protected]c4f883a2012-02-03 17:02:0780 UtilityProcessHost* host =
81 UtilityProcessHost::Create(this, BrowserThread::IO);
82 host->EnableZygote();
83 host->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_));
[email protected]8915f342011-08-29 22:14:3784 }
85
[email protected]c4f883a2012-02-03 17:02:0786 // Implementing pieces of the UtilityProcessHostClient interface.
[email protected]8915f342011-08-29 22:14:3787 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
88 bool handled = true;
89 IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message)
90 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded,
91 OnJSONParseSucceeded)
92 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed,
93 OnJSONParseFailed)
94 IPC_MESSAGE_UNHANDLED(handled = false)
95 IPC_END_MESSAGE_MAP()
96 return handled;
97 }
98
99 void OnJSONParseSucceeded(const ListValue& wrapper) {
100 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
101 Value* value = NULL;
102 CHECK(wrapper.Get(0, &value));
103 if (value->IsType(Value::TYPE_DICTIONARY)) {
104 parsed_webstore_data_.reset(
105 static_cast<DictionaryValue*>(value)->DeepCopy());
106 } else {
107 error_ = kInvalidWebstoreResponseError;
108 }
109
110 ReportResults();
111 }
112
113 virtual void OnJSONParseFailed(const std::string& error_message) {
114 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
115 error_ = error_message;
116 ReportResults();
117 }
118
119 void ReportResults() {
120 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
121
[email protected]8915f342011-08-29 22:14:37122 BrowserThread::PostTask(
123 BrowserThread::UI,
124 FROM_HERE,
[email protected]8e6ac4b2011-10-17 19:04:31125 base::Bind(&SafeWebstoreResponseParser::ReportResultOnUIThread, this));
[email protected]8915f342011-08-29 22:14:37126 }
127
128 void ReportResultOnUIThread() {
129 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
130 if (error_.empty() && parsed_webstore_data_.get()) {
131 client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release());
132 } else {
133 client_->OnWebstoreResponseParseFailure(error_);
134 }
135 }
136
137 private:
138 virtual ~SafeWebstoreResponseParser() {}
139
140 WebstoreInlineInstaller* client_;
141
142 std::string webstore_data_;
[email protected]8915f342011-08-29 22:14:37143 std::string error_;
144 scoped_ptr<DictionaryValue> parsed_webstore_data_;
145};
146
[email protected]26b5e322011-12-23 01:36:47147WebstoreInlineInstaller::WebstoreInlineInstaller(WebContents* web_contents,
[email protected]a221ef092011-09-07 01:34:10148 int install_id,
[email protected]7b921042012-02-11 01:41:27149 int return_route_id,
[email protected]8915f342011-08-29 22:14:37150 std::string webstore_item_id,
[email protected]a221ef092011-09-07 01:34:10151 GURL requestor_url,
[email protected]8915f342011-08-29 22:14:37152 Delegate* delegate)
[email protected]26b5e322011-12-23 01:36:47153 : content::WebContentsObserver(web_contents),
[email protected]a221ef092011-09-07 01:34:10154 install_id_(install_id),
[email protected]7b921042012-02-11 01:41:27155 return_route_id_(return_route_id),
[email protected]8915f342011-08-29 22:14:37156 id_(webstore_item_id),
[email protected]a221ef092011-09-07 01:34:10157 requestor_url_(requestor_url),
[email protected]c7bf7452011-09-12 21:31:50158 delegate_(delegate),
159 average_rating_(0.0),
160 rating_count_(0) {}
[email protected]8915f342011-08-29 22:14:37161
162WebstoreInlineInstaller::~WebstoreInlineInstaller() {
163}
164
165void WebstoreInlineInstaller::BeginInstall() {
[email protected]26b5e322011-12-23 01:36:47166 AddRef(); // Balanced in CompleteInstall or WebContentsDestroyed.
[email protected]8915f342011-08-29 22:14:37167
168 if (!Extension::IdIsValid(id_)) {
169 CompleteInstall(kInvalidWebstoreItemId);
170 return;
171 }
172
173 GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_));
174
[email protected]36aea2702011-10-26 01:12:22175 webstore_data_url_fetcher_.reset(content::URLFetcher::Create(
176 webstore_data_url, content::URLFetcher::GET, this));
[email protected]8915f342011-08-29 22:14:37177 Profile* profile = Profile::FromBrowserContext(
[email protected]ea049a02011-12-25 21:37:09178 web_contents()->GetBrowserContext());
[email protected]7cc6e5632011-10-25 17:56:12179 webstore_data_url_fetcher_->SetRequestContext(
[email protected]8915f342011-08-29 22:14:37180 profile->GetRequestContext());
[email protected]b4574c02011-11-17 06:19:13181 // Use the requesting page as the referrer both since that is more correct
182 // (it is the page that caused this request to happen) and so that we can
183 // track top sites that trigger inline install requests.
184 webstore_data_url_fetcher_->SetReferrer(requestor_url_.spec());
[email protected]7cc6e5632011-10-25 17:56:12185 webstore_data_url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
186 net::LOAD_DO_NOT_SAVE_COOKIES |
187 net::LOAD_DISABLE_CACHE);
[email protected]8915f342011-08-29 22:14:37188 webstore_data_url_fetcher_->Start();
189}
190
[email protected]7cc6e5632011-10-25 17:56:12191void WebstoreInlineInstaller::OnURLFetchComplete(
192 const content::URLFetcher* source) {
[email protected]8915f342011-08-29 22:14:37193 CHECK_EQ(webstore_data_url_fetcher_.get(), source);
[email protected]26b5e322011-12-23 01:36:47194 // We shouldn't be getting UrlFetcher callbacks if the WebContents has gone
195 // away; we stop any in in-progress fetches in WebContentsDestroyed.
[email protected]ea049a02011-12-25 21:37:09196 CHECK(web_contents());
[email protected]8915f342011-08-29 22:14:37197
[email protected]7cc6e5632011-10-25 17:56:12198 if (!webstore_data_url_fetcher_->GetStatus().is_success() ||
199 webstore_data_url_fetcher_->GetResponseCode() != 200) {
[email protected]8915f342011-08-29 22:14:37200 CompleteInstall(kWebstoreRequestError);
201 return;
202 }
203
204 std::string webstore_json_data;
205 webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data);
206 webstore_data_url_fetcher_.reset();
207
208 scoped_refptr<SafeWebstoreResponseParser> parser =
209 new SafeWebstoreResponseParser(this, webstore_json_data);
210 // The parser will call us back via OnWebstoreResponseParseSucces or
211 // OnWebstoreResponseParseFailure.
212 parser->Start();
213}
214
215void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess(
216 DictionaryValue* webstore_data) {
[email protected]4693243e2011-09-09 23:52:37217 // Check if the tab has gone away in the meantime.
[email protected]ea049a02011-12-25 21:37:09218 if (!web_contents()) {
[email protected]4693243e2011-09-09 23:52:37219 CompleteInstall("");
220 return;
221 }
222
[email protected]8915f342011-08-29 22:14:37223 webstore_data_.reset(webstore_data);
224
[email protected]dd5161a2011-09-14 17:40:17225 // The store may not support inline installs for this item, in which case
226 // we open the store-provided redirect URL in a new tab and abort the
227 // installation process.
228 bool inline_install_not_supported = false;
229 if (webstore_data->HasKey(kInlineInstallNotSupportedKey) &&
230 !webstore_data->GetBoolean(
231 kInlineInstallNotSupportedKey, &inline_install_not_supported)) {
[email protected]8915f342011-08-29 22:14:37232 CompleteInstall(kInvalidWebstoreResponseError);
233 return;
234 }
[email protected]dd5161a2011-09-14 17:40:17235 if (inline_install_not_supported) {
236 std::string redirect_url;
237 if (!webstore_data->GetString(kRedirectUrlKey, &redirect_url)) {
238 CompleteInstall(kInvalidWebstoreResponseError);
239 return;
240 }
[email protected]8915f342011-08-29 22:14:37241
[email protected]ea049a02011-12-25 21:37:09242 web_contents()->OpenURL(OpenURLParams(
[email protected]dd5161a2011-09-14 17:40:17243 GURL(redirect_url),
[email protected]ea049a02011-12-25 21:37:09244 content::Referrer(web_contents()->GetURL(),
[email protected]bce1f1c2011-12-05 15:11:58245 WebKit::WebReferrerPolicyDefault),
[email protected]dd5161a2011-09-14 17:40:17246 NEW_FOREGROUND_TAB,
[email protected]e47ae9472011-10-13 19:48:34247 content::PAGE_TRANSITION_AUTO_BOOKMARK,
248 false));
[email protected]dd5161a2011-09-14 17:40:17249 CompleteInstall(kInlineInstallSupportedError);
250 return;
251 }
252
253 // Manifest, number of users, average rating and rating count are required.
254 std::string manifest;
255 if (!webstore_data->GetString(kManifestKey, &manifest) ||
256 !webstore_data->GetString(kUsersKey, &localized_user_count_) ||
[email protected]5fdbd6f2011-09-01 17:33:04257 !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
258 !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
259 CompleteInstall(kInvalidWebstoreResponseError);
260 return;
261 }
262
263 if (average_rating_ < ExtensionInstallUI::kMinExtensionRating ||
264 average_rating_ >ExtensionInstallUI::kMaxExtensionRating) {
265 CompleteInstall(kInvalidWebstoreResponseError);
266 return;
267 }
268
269 // Localized name and description are optional.
270 if ((webstore_data->HasKey(kLocalizedNameKey) &&
271 !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) ||
272 (webstore_data->HasKey(kLocalizedDescriptionKey) &&
273 !webstore_data->GetString(
274 kLocalizedDescriptionKey, &localized_description_))) {
[email protected]8915f342011-08-29 22:14:37275 CompleteInstall(kInvalidWebstoreResponseError);
276 return;
277 }
278
279 // Icon URL is optional.
280 GURL icon_url;
281 if (webstore_data->HasKey(kIconUrlKey)) {
282 std::string icon_url_string;
283 if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
284 CompleteInstall(kInvalidWebstoreResponseError);
285 return;
286 }
287 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
288 icon_url_string);
289 if (!icon_url.is_valid()) {
290 CompleteInstall(kInvalidWebstoreResponseError);
291 return;
292 }
293 }
294
[email protected]5da311ea2011-09-14 20:57:32295 // Verified site is required
[email protected]a221ef092011-09-07 01:34:10296 if (webstore_data->HasKey(kVerifiedSiteKey)) {
[email protected]bdb74a22012-01-25 20:33:33297 std::string verified_site;
298 if (!webstore_data->GetString(kVerifiedSiteKey, &verified_site)) {
[email protected]a221ef092011-09-07 01:34:10299 CompleteInstall(kInvalidWebstoreResponseError);
300 return;
301 }
302
[email protected]bdb74a22012-01-25 20:33:33303 if (!IsRequestorURLInVerifiedSite(requestor_url_, verified_site)) {
[email protected]5da311ea2011-09-14 20:57:32304 CompleteInstall(kNotFromVerifiedSiteError);
[email protected]a221ef092011-09-07 01:34:10305 return;
306 }
[email protected]5da311ea2011-09-14 20:57:32307 } else {
308 CompleteInstall(kNoVerifiedSiteError);
309 return;
[email protected]a221ef092011-09-07 01:34:10310 }
311
[email protected]8915f342011-08-29 22:14:37312 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
313 this,
[email protected]98e4e522011-10-25 13:00:16314 id_,
[email protected]8915f342011-08-29 22:14:37315 manifest,
316 "", // We don't have any icon data.
317 icon_url,
[email protected]ea049a02011-12-25 21:37:09318 Profile::FromBrowserContext(web_contents()->GetBrowserContext())->
[email protected]8915f342011-08-29 22:14:37319 GetRequestContext());
320 // The helper will call us back via OnWebstoreParseSucces or
321 // OnWebstoreParseFailure.
322 helper->Start();
323}
324
[email protected]bdb74a22012-01-25 20:33:33325// static
326bool WebstoreInlineInstaller::IsRequestorURLInVerifiedSite(
327 const GURL& requestor_url,
328 const std::string& verified_site) {
329 // Turn the verified site (which may be a bare domain, or have a port and/or a
330 // path) into a URL that can be parsed by URLPattern.
331 std::string verified_site_url =
332 StringPrintf("http://*.%s%s",
333 verified_site.c_str(),
334 verified_site.find('/') == std::string::npos ? "/*" : "*");
335
336 URLPattern verified_site_pattern(
337 URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS);
338 URLPattern::ParseResult parse_result =
339 verified_site_pattern.Parse(verified_site_url);
340 if (parse_result != URLPattern::PARSE_SUCCESS) {
341 DLOG(WARNING) << "Could not parse " << verified_site_url <<
342 " as URL pattern " << parse_result;
343 return false;
344 }
345 verified_site_pattern.SetScheme("*");
346
347 return verified_site_pattern.MatchesURL(requestor_url);
348}
349
[email protected]8915f342011-08-29 22:14:37350void WebstoreInlineInstaller::OnWebstoreResponseParseFailure(
351 const std::string& error) {
352 CompleteInstall(error);
353}
354
355void WebstoreInlineInstaller::OnWebstoreParseSuccess(
[email protected]98e4e522011-10-25 13:00:16356 const std::string& id,
[email protected]8915f342011-08-29 22:14:37357 const SkBitmap& icon,
358 base::DictionaryValue* manifest) {
[email protected]2fd920fb2011-09-08 23:33:00359 // Check if the tab has gone away in the meantime.
[email protected]ea049a02011-12-25 21:37:09360 if (!web_contents()) {
[email protected]2fd920fb2011-09-08 23:33:00361 CompleteInstall("");
362 return;
363 }
364
[email protected]98e4e522011-10-25 13:00:16365 CHECK_EQ(id_, id);
[email protected]8915f342011-08-29 22:14:37366 manifest_.reset(manifest);
367 icon_ = icon;
368
369 Profile* profile = Profile::FromBrowserContext(
[email protected]ea049a02011-12-25 21:37:09370 web_contents()->GetBrowserContext());
[email protected]5fdbd6f2011-09-01 17:33:04371
372 ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT);
[email protected]122e8df2011-09-02 18:20:24373 prompt.SetInlineInstallWebstoreData(localized_user_count_,
374 average_rating_,
375 rating_count_);
[email protected]514c5472012-04-20 22:56:36376 std::string error;
377 dummy_extension_ = ExtensionInstallUI::GetLocalizedExtensionForDisplay(
378 manifest, id_, localized_name_, localized_description_, &error);
379 if (!dummy_extension_) {
380 OnWebstoreParseFailure(id_, WebstoreInstallHelper::Delegate::MANIFEST_ERROR,
381 kInvalidManifestError);
[email protected]8915f342011-08-29 22:14:37382 return;
383 }
384
[email protected]514c5472012-04-20 22:56:36385 install_ui_.reset(new ExtensionInstallUI(profile));
386 install_ui_->ConfirmInlineInstall(this, dummy_extension_, &icon_, prompt);
[email protected]8915f342011-08-29 22:14:37387 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
388}
389
390void WebstoreInlineInstaller::OnWebstoreParseFailure(
[email protected]98e4e522011-10-25 13:00:16391 const std::string& id,
[email protected]8915f342011-08-29 22:14:37392 InstallHelperResultCode result_code,
393 const std::string& error_message) {
394 CompleteInstall(error_message);
395}
396
397void WebstoreInlineInstaller::InstallUIProceed() {
[email protected]2fd920fb2011-09-08 23:33:00398 // Check if the tab has gone away in the meantime.
[email protected]ea049a02011-12-25 21:37:09399 if (!web_contents()) {
[email protected]2fd920fb2011-09-08 23:33:00400 CompleteInstall("");
401 return;
402 }
403
[email protected]655b2b1a2011-10-13 17:13:06404 Profile* profile = Profile::FromBrowserContext(
[email protected]ea049a02011-12-25 21:37:09405 web_contents()->GetBrowserContext());
[email protected]8915f342011-08-29 22:14:37406
[email protected]21a5ad62012-04-03 04:48:45407 scoped_ptr<WebstoreInstaller::Approval> approval(
408 new WebstoreInstaller::Approval);
409 approval->extension_id = id_;
410 approval->profile = profile;
411 approval->parsed_manifest.reset(manifest_.get()->DeepCopy());
412 approval->use_app_installed_bubble = true;
413
[email protected]98e4e522011-10-25 13:00:16414 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
[email protected]21a5ad62012-04-03 04:48:45415 profile, this, &(web_contents()->GetController()), id_, approval.Pass(),
[email protected]98e4e522011-10-25 13:00:16416 WebstoreInstaller::FLAG_INLINE_INSTALL);
417 installer->Start();
[email protected]8915f342011-08-29 22:14:37418}
419
420void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) {
421 CompleteInstall(kUserCancelledError);
422}
423
[email protected]26b5e322011-12-23 01:36:47424void WebstoreInlineInstaller::WebContentsDestroyed(WebContents* web_contents) {
[email protected]2fd920fb2011-09-08 23:33:00425 // Abort any in-progress fetches.
426 if (webstore_data_url_fetcher_.get()) {
427 webstore_data_url_fetcher_.reset();
428 Release(); // Matches the AddRef in BeginInstall.
[email protected]8915f342011-08-29 22:14:37429 }
[email protected]2fd920fb2011-09-08 23:33:00430}
431
[email protected]cb08ba22011-10-19 21:41:40432void WebstoreInlineInstaller::OnExtensionInstallSuccess(const std::string& id) {
433 CHECK_EQ(id_, id);
434 CompleteInstall("");
435}
436
437void WebstoreInlineInstaller::OnExtensionInstallFailure(
438 const std::string& id, const std::string& error) {
439 CHECK_EQ(id_, id);
440 CompleteInstall(error);
441}
442
[email protected]2fd920fb2011-09-08 23:33:00443void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
444 // Only bother responding if there's still a tab contents to send back the
445 // response to.
[email protected]ea049a02011-12-25 21:37:09446 if (web_contents()) {
[email protected]2fd920fb2011-09-08 23:33:00447 if (error.empty()) {
[email protected]7b921042012-02-11 01:41:27448 delegate_->OnInlineInstallSuccess(install_id_, return_route_id_);
[email protected]2fd920fb2011-09-08 23:33:00449 } else {
[email protected]7b921042012-02-11 01:41:27450 delegate_->OnInlineInstallFailure(install_id_, return_route_id_, error);
[email protected]2fd920fb2011-09-08 23:33:00451 }
452 }
453
[email protected]8915f342011-08-29 22:14:37454 Release(); // Matches the AddRef in BeginInstall.
455}