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"
30 namespace extensions
{
34 enum AutoApproveForTest
{
40 AutoApproveForTest g_auto_approve_for_test
= DO_NOT_SKIP
;
42 // Creates a dummy extension and sets the manifest's name to the item's
44 scoped_refptr
<Extension
> CreateDummyExtension(
45 const BundleInstaller::Item
& item
,
46 base::DictionaryValue
* manifest
,
47 content::BrowserContext
* browser_context
) {
48 // We require localized names so we can have nice error messages when we can't
49 // parse an extension manifest.
50 CHECK(!item
.localized_name
.empty());
53 scoped_refptr
<Extension
> extension
= Extension::Create(base::FilePath(),
59 // Initialize permissions so that withheld permissions are displayed properly
60 // in the install prompt.
61 PermissionsUpdater(browser_context
, PermissionsUpdater::INIT_FLAG_TRANSIENT
)
62 .InitializePermissions(extension
.get());
66 bool IsAppPredicate(scoped_refptr
<const Extension
> extension
) {
67 return extension
->is_app();
70 struct MatchIdFunctor
{
71 explicit MatchIdFunctor(const std::string
& id
) : id(id
) {}
72 bool operator()(scoped_refptr
<const Extension
> extension
) {
73 return extension
->id() == id
;
78 // Holds the message IDs for BundleInstaller::GetHeadingTextFor.
79 const int kHeadingIds
[3][4] = {
82 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSIONS
,
83 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_APPS
,
84 IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSION_APPS
88 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSIONS
,
89 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_APPS
,
90 IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSION_APPS
97 void BundleInstaller::SetAutoApproveForTesting(bool auto_approve
) {
98 CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType
));
99 g_auto_approve_for_test
= auto_approve
? PROCEED
: ABORT
;
102 BundleInstaller::Item::Item() : state(STATE_PENDING
) {}
104 base::string16
BundleInstaller::Item::GetNameForDisplay() {
105 base::string16 name
= base::UTF8ToUTF16(localized_name
);
106 base::i18n::AdjustStringForLocaleDirection(&name
);
107 return l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE
, name
);
110 BundleInstaller::BundleInstaller(Browser
* browser
,
111 const BundleInstaller::ItemList
& items
)
114 host_desktop_type_(browser
->host_desktop_type()),
115 profile_(browser
->profile()),
117 BrowserList::AddObserver(this);
118 for (size_t i
= 0; i
< items
.size(); ++i
) {
119 items_
[items
[i
].id
] = items
[i
];
120 items_
[items
[i
].id
].state
= Item::STATE_PENDING
;
124 BundleInstaller::ItemList
BundleInstaller::GetItemsWithState(
125 Item::State state
) const {
128 for (ItemMap::const_iterator i
= items_
.begin(); i
!= items_
.end(); ++i
) {
129 if (i
->second
.state
== state
)
130 list
.push_back(i
->second
);
136 void BundleInstaller::PromptForApproval(Delegate
* delegate
) {
137 delegate_
= delegate
;
139 AddRef(); // Balanced in ReportApproved() and ReportCanceled().
144 void BundleInstaller::CompleteInstall(content::WebContents
* web_contents
,
145 Delegate
* delegate
) {
148 delegate_
= delegate
;
150 AddRef(); // Balanced in ReportComplete();
152 if (GetItemsWithState(Item::STATE_PENDING
).empty()) {
157 // Start each WebstoreInstaller.
158 for (ItemMap::iterator i
= items_
.begin(); i
!= items_
.end(); ++i
) {
159 if (i
->second
.state
!= Item::STATE_PENDING
)
162 // Since we've already confirmed the permissions, create an approval that
163 // lets CrxInstaller bypass the prompt.
164 scoped_ptr
<WebstoreInstaller::Approval
> approval(
165 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
168 scoped_ptr
<base::DictionaryValue
>(
169 parsed_manifests_
[i
->first
]->DeepCopy()), true));
170 approval
->use_app_installed_bubble
= false;
171 approval
->skip_post_install_ui
= true;
173 scoped_refptr
<WebstoreInstaller
> installer
= new WebstoreInstaller(
179 WebstoreInstaller::INSTALL_SOURCE_OTHER
);
184 base::string16
BundleInstaller::GetHeadingTextFor(Item::State state
) const {
185 // For STATE_FAILED, we can't tell if the items were apps or extensions
186 // so we always show the same message.
187 if (state
== Item::STATE_FAILED
) {
188 if (GetItemsWithState(state
).size())
189 return l10n_util::GetStringUTF16(IDS_EXTENSION_BUNDLE_ERROR_HEADING
);
190 return base::string16();
193 size_t total
= GetItemsWithState(state
).size();
194 size_t apps
= std::count_if(
195 dummy_extensions_
.begin(), dummy_extensions_
.end(), &IsAppPredicate
);
197 bool has_apps
= apps
> 0;
198 bool has_extensions
= apps
< total
;
199 size_t index
= (has_extensions
<< 0) + (has_apps
<< 1);
201 CHECK_LT(static_cast<size_t>(state
), arraysize(kHeadingIds
));
202 CHECK_LT(index
, arraysize(kHeadingIds
[state
]));
204 int msg_id
= kHeadingIds
[state
][index
];
206 return base::string16();
208 return l10n_util::GetStringUTF16(msg_id
);
211 BundleInstaller::~BundleInstaller() {
212 BrowserList::RemoveObserver(this);
215 void BundleInstaller::ParseManifests() {
216 if (items_
.empty()) {
217 ReportCanceled(false);
221 for (ItemMap::iterator i
= items_
.begin(); i
!= items_
.end(); ++i
) {
222 scoped_refptr
<WebstoreInstallHelper
> helper
= new WebstoreInstallHelper(
223 this, i
->first
, i
->second
.manifest
, std::string(), GURL(), NULL
);
228 void BundleInstaller::ReportApproved() {
230 delegate_
->OnBundleInstallApproved();
232 Release(); // Balanced in PromptForApproval().
235 void BundleInstaller::ReportCanceled(bool user_initiated
) {
237 delegate_
->OnBundleInstallCanceled(user_initiated
);
239 Release(); // Balanced in PromptForApproval().
242 void BundleInstaller::ReportComplete() {
244 delegate_
->OnBundleInstallCompleted();
246 Release(); // Balanced in CompleteInstall().
249 void BundleInstaller::ShowPromptIfDoneParsing() {
250 // We don't prompt until all the manifests have been parsed.
251 ItemList pending_items
= GetItemsWithState(Item::STATE_PENDING
);
252 if (pending_items
.size() != dummy_extensions_
.size())
258 void BundleInstaller::ShowPrompt() {
259 // Abort if we couldn't create any Extensions out of the manifests.
260 if (dummy_extensions_
.empty()) {
261 ReportCanceled(false);
265 scoped_refptr
<PermissionSet
> permissions
;
266 for (size_t i
= 0; i
< dummy_extensions_
.size(); ++i
) {
267 permissions
= PermissionSet::CreateUnion(
269 dummy_extensions_
[i
]->permissions_data()->active_permissions().get());
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 install_ui_
->ConfirmBundleInstall(this, permissions
.get());
291 void BundleInstaller::ShowInstalledBubbleIfDone() {
292 // We're ready to show the installed bubble when no items are pending.
293 if (!GetItemsWithState(Item::STATE_PENDING
).empty())
297 ShowInstalledBubble(this, browser_
);
302 void BundleInstaller::OnWebstoreParseSuccess(
303 const std::string
& id
,
304 const SkBitmap
& icon
,
305 base::DictionaryValue
* manifest
) {
306 dummy_extensions_
.push_back(
307 CreateDummyExtension(items_
[id
], manifest
, profile_
));
308 parsed_manifests_
[id
] = linked_ptr
<base::DictionaryValue
>(manifest
);
310 ShowPromptIfDoneParsing();
313 void BundleInstaller::OnWebstoreParseFailure(
314 const std::string
& id
,
315 WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code
,
316 const std::string
& error_message
) {
317 items_
[id
].state
= Item::STATE_FAILED
;
319 ShowPromptIfDoneParsing();
322 void BundleInstaller::InstallUIProceed() {
327 void BundleInstaller::InstallUIAbort(bool user_initiated
) {
328 for (ItemMap::iterator i
= items_
.begin(); i
!= items_
.end(); ++i
)
329 i
->second
.state
= Item::STATE_FAILED
;
331 ReportCanceled(user_initiated
);
334 void BundleInstaller::OnExtensionInstallSuccess(const std::string
& id
) {
335 items_
[id
].state
= Item::STATE_INSTALLED
;
337 ShowInstalledBubbleIfDone();
340 void BundleInstaller::OnExtensionInstallFailure(
341 const std::string
& id
,
342 const std::string
& error
,
343 WebstoreInstaller::FailureReason reason
) {
344 items_
[id
].state
= Item::STATE_FAILED
;
346 ExtensionList::iterator i
= std::find_if(
347 dummy_extensions_
.begin(), dummy_extensions_
.end(), MatchIdFunctor(id
));
348 CHECK(dummy_extensions_
.end() != i
);
349 dummy_extensions_
.erase(i
);
351 ShowInstalledBubbleIfDone();
354 void BundleInstaller::OnBrowserAdded(Browser
* browser
) {}
356 void BundleInstaller::OnBrowserRemoved(Browser
* browser
) {
357 if (browser_
== browser
)
361 void BundleInstaller::OnBrowserSetLastActive(Browser
* browser
) {}
363 } // namespace extensions