1 // Copyright (c) 2012 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.
5 #include "chrome/browser/extensions/bundle_installer.h"
11 #include "base/command_line.h"
12 #include "base/i18n/rtl.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "chrome/browser/extensions/crx_installer.h"
16 #include "chrome/browser/extensions/permissions_updater.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_finder.h"
20 #include "chrome/browser/ui/browser_list.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "chrome/grit/generated_resources.h"
24 #include "content/public/browser/web_contents.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/permissions/permission_set.h"
27 #include "extensions/common/permissions/permissions_data.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/gfx/image/image_skia.h"
31 namespace extensions
{
35 enum AutoApproveForTest
{
41 AutoApproveForTest g_auto_approve_for_test
= DO_NOT_SKIP
;
43 scoped_refptr
<Extension
> CreateDummyExtension(
44 const BundleInstaller::Item
& item
,
45 const base::DictionaryValue
& manifest
,
46 content::BrowserContext
* browser_context
) {
47 // We require localized names so we can have nice error messages when we can't
48 // parse an extension manifest.
49 CHECK(!item
.localized_name
.empty());
52 scoped_refptr
<Extension
> extension
= Extension::Create(base::FilePath(),
58 // Initialize permissions so that withheld permissions are displayed properly
59 // in the install prompt.
60 PermissionsUpdater(browser_context
, PermissionsUpdater::INIT_FLAG_TRANSIENT
)
61 .InitializePermissions(extension
.get());
65 const int kHeadingIdsInstallPrompt
[] = {
66 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_TITLE_EXTENSIONS
,
67 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_TITLE_APPS
,
68 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_TITLE_EXTENSION_APPS
71 const int kHeadingIdsDelegatedInstallPrompt
[] = {
72 IDS_EXTENSION_BUNDLE_DELEGATED_INSTALL_PROMPT_TITLE_EXTENSIONS
,
73 IDS_EXTENSION_BUNDLE_DELEGATED_INSTALL_PROMPT_TITLE_APPS
,
74 IDS_EXTENSION_BUNDLE_DELEGATED_INSTALL_PROMPT_TITLE_EXTENSION_APPS
77 const int kHeadingIdsInstalledBubble
[] = {
78 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSIONS
,
79 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_APPS
,
80 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSION_APPS
86 void BundleInstaller::SetAutoApproveForTesting(bool auto_approve
) {
87 CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType
));
88 g_auto_approve_for_test
= auto_approve
? PROCEED
: ABORT
;
91 BundleInstaller::Item::Item() : state(STATE_PENDING
) {}
93 BundleInstaller::Item::~Item() {}
95 base::string16
BundleInstaller::Item::GetNameForDisplay() const {
96 base::string16 name
= base::UTF8ToUTF16(localized_name
);
97 base::i18n::AdjustStringForLocaleDirection(&name
);
101 BundleInstaller::BundleInstaller(Browser
* browser
,
102 const std::string
& name
,
103 const SkBitmap
& icon
,
104 const std::string
& authuser
,
105 const std::string
& delegated_username
,
106 const BundleInstaller::ItemList
& items
)
112 delegated_username_(delegated_username
),
113 host_desktop_type_(browser
->host_desktop_type()),
114 profile_(browser
->profile()) {
115 BrowserList::AddObserver(this);
116 for (size_t i
= 0; i
< items
.size(); ++i
) {
117 items_
[items
[i
].id
] = items
[i
];
118 items_
[items
[i
].id
].state
= Item::STATE_PENDING
;
122 BundleInstaller::~BundleInstaller() {
123 BrowserList::RemoveObserver(this);
126 BundleInstaller::ItemList
BundleInstaller::GetItemsWithState(
127 Item::State state
) const {
130 for (const std::pair
<std::string
, Item
>& entry
: items_
) {
131 if (entry
.second
.state
== state
)
132 list
.push_back(entry
.second
);
138 bool BundleInstaller::HasItemWithState(Item::State state
) const {
139 return CountItemsWithState(state
) > 0;
142 size_t BundleInstaller::CountItemsWithState(Item::State state
) const {
143 return std::count_if(items_
.begin(), items_
.end(),
144 [state
] (const std::pair
<std::string
, Item
>& entry
) {
145 return entry
.second
.state
== state
;
149 void BundleInstaller::PromptForApproval(const ApprovalCallback
& callback
) {
150 approval_callback_
= callback
;
155 void BundleInstaller::CompleteInstall(content::WebContents
* web_contents
,
156 const base::Closure
& callback
) {
157 DCHECK(web_contents
);
160 install_callback_
= callback
;
162 if (!HasItemWithState(Item::STATE_PENDING
)) {
163 install_callback_
.Run();
167 // Start each WebstoreInstaller.
168 for (const std::pair
<std::string
, Item
>& entry
: items_
) {
169 if (entry
.second
.state
!= Item::STATE_PENDING
)
172 // Since we've already confirmed the permissions, create an approval that
173 // lets CrxInstaller bypass the prompt.
174 scoped_ptr
<WebstoreInstaller::Approval
> approval(
175 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
178 make_scoped_ptr(parsed_manifests_
[entry
.first
]->DeepCopy()),
180 approval
->use_app_installed_bubble
= false;
181 approval
->skip_post_install_ui
= true;
182 approval
->authuser
= authuser_
;
183 approval
->installing_icon
=
184 gfx::ImageSkia::CreateFrom1xBitmap(entry
.second
.icon
);
186 scoped_refptr
<WebstoreInstaller
> installer
= new WebstoreInstaller(
192 WebstoreInstaller::INSTALL_SOURCE_OTHER
);
197 base::string16
BundleInstaller::GetHeadingTextFor(Item::State state
) const {
198 // For STATE_FAILED, we can't tell if the items were apps or extensions
199 // so we always show the same message.
200 if (state
== Item::STATE_FAILED
) {
201 if (HasItemWithState(state
))
202 return l10n_util::GetStringUTF16(IDS_EXTENSION_BUNDLE_ERROR_HEADING
);
203 return base::string16();
206 size_t total
= CountItemsWithState(state
);
208 return base::string16();
210 size_t apps
= std::count_if(
211 dummy_extensions_
.begin(), dummy_extensions_
.end(),
212 [] (const scoped_refptr
<const Extension
>& ext
) { return ext
->is_app(); });
214 bool has_apps
= apps
> 0;
215 bool has_extensions
= apps
< total
;
216 size_t index
= (has_extensions
<< 0) + (has_apps
<< 1) - 1;
218 if (state
== Item::STATE_PENDING
) {
219 if (!delegated_username_
.empty()) {
220 return l10n_util::GetStringFUTF16(
221 kHeadingIdsDelegatedInstallPrompt
[index
], base::UTF8ToUTF16(name_
),
222 base::UTF8ToUTF16(delegated_username_
));
224 return l10n_util::GetStringFUTF16(kHeadingIdsInstallPrompt
[index
],
225 base::UTF8ToUTF16(name_
));
228 return l10n_util::GetStringUTF16(kHeadingIdsInstalledBubble
[index
]);
232 void BundleInstaller::ParseManifests() {
233 if (items_
.empty()) {
234 approval_callback_
.Run(APPROVAL_ERROR
);
238 net::URLRequestContextGetter
* context_getter
=
239 browser_
? browser_
->profile()->GetRequestContext() : nullptr;
241 for (const std::pair
<std::string
, Item
>& entry
: items_
) {
242 scoped_refptr
<WebstoreInstallHelper
> helper
= new WebstoreInstallHelper(
243 this, entry
.first
, entry
.second
.manifest
, entry
.second
.icon_url
,
249 void BundleInstaller::ShowPromptIfDoneParsing() {
250 // We don't prompt until all the manifests have been parsed.
251 if (CountItemsWithState(Item::STATE_PENDING
) != dummy_extensions_
.size())
257 void BundleInstaller::ShowPrompt() {
258 // Abort if we couldn't create any Extensions out of the manifests.
259 if (dummy_extensions_
.empty()) {
260 approval_callback_
.Run(APPROVAL_ERROR
);
264 scoped_refptr
<const PermissionSet
> permissions
=
265 dummy_extensions_
[0]->permissions_data()->active_permissions();
266 for (size_t i
= 1; i
< dummy_extensions_
.size(); ++i
) {
267 permissions
= PermissionSet::CreateUnion(
269 *dummy_extensions_
[i
]->permissions_data()->active_permissions());
272 if (g_auto_approve_for_test
== PROCEED
) {
274 } else if (g_auto_approve_for_test
== ABORT
) {
275 InstallUIAbort(true);
277 Browser
* browser
= browser_
;
279 // The browser that we got initially could have gone away during our
281 browser
= chrome::FindLastActiveWithProfile(profile_
, host_desktop_type_
);
283 content::WebContents
* web_contents
= NULL
;
285 web_contents
= browser
->tab_strip_model()->GetActiveWebContents();
286 install_ui_
.reset(new ExtensionInstallPrompt(web_contents
));
287 if (delegated_username_
.empty()) {
288 install_ui_
->ConfirmBundleInstall(this, &icon_
, permissions
.get());
290 install_ui_
->ConfirmPermissionsForDelegatedBundleInstall(
291 this, delegated_username_
, &icon_
, permissions
.get());
296 void BundleInstaller::ShowInstalledBubbleIfDone() {
297 // We're ready to show the installed bubble when no items are pending.
298 if (HasItemWithState(Item::STATE_PENDING
))
302 ShowInstalledBubble(this, browser_
);
304 install_callback_
.Run();
307 void BundleInstaller::OnWebstoreParseSuccess(
308 const std::string
& id
,
309 const SkBitmap
& icon
,
310 base::DictionaryValue
* manifest
) {
311 items_
[id
].icon
= icon
;
312 dummy_extensions_
.push_back(
313 CreateDummyExtension(items_
[id
], *manifest
, profile_
));
314 parsed_manifests_
[id
] = linked_ptr
<base::DictionaryValue
>(manifest
);
316 ShowPromptIfDoneParsing();
319 void BundleInstaller::OnWebstoreParseFailure(
320 const std::string
& id
,
321 WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code
,
322 const std::string
& error_message
) {
323 items_
[id
].state
= Item::STATE_FAILED
;
325 ShowPromptIfDoneParsing();
328 void BundleInstaller::InstallUIProceed() {
330 approval_callback_
.Run(APPROVED
);
333 void BundleInstaller::InstallUIAbort(bool user_initiated
) {
334 for (std::pair
<const std::string
, Item
>& entry
: items_
)
335 entry
.second
.state
= Item::STATE_FAILED
;
337 approval_callback_
.Run(user_initiated
? USER_CANCELED
: APPROVAL_ERROR
);
340 void BundleInstaller::OnExtensionInstallSuccess(const std::string
& id
) {
341 items_
[id
].state
= Item::STATE_INSTALLED
;
343 ShowInstalledBubbleIfDone();
346 void BundleInstaller::OnExtensionInstallFailure(
347 const std::string
& id
,
348 const std::string
& error
,
349 WebstoreInstaller::FailureReason reason
) {
350 items_
[id
].state
= Item::STATE_FAILED
;
352 ExtensionList::iterator i
= std::find_if(
353 dummy_extensions_
.begin(), dummy_extensions_
.end(),
354 [&id
] (const scoped_refptr
<const Extension
>& ext
) {
355 return ext
->id() == id
;
357 CHECK(dummy_extensions_
.end() != i
);
358 dummy_extensions_
.erase(i
);
360 ShowInstalledBubbleIfDone();
363 void BundleInstaller::OnBrowserRemoved(Browser
* browser
) {
364 if (browser_
== browser
)
368 } // namespace extensions