Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / extensions / bundle_installer.cc
blob136b5f0276747095cad96603a63fce8be81dd18d
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"
7 #include <algorithm>
8 #include <string>
9 #include <vector>
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 {
33 namespace {
35 enum AutoApproveForTest {
36 DO_NOT_SKIP = 0,
37 PROCEED,
38 ABORT
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());
51 std::string error;
52 scoped_refptr<Extension> extension = Extension::Create(base::FilePath(),
53 Manifest::INTERNAL,
54 manifest,
55 Extension::NO_FLAGS,
56 item.id,
57 &error);
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());
62 return extension;
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
83 } // namespace
85 // static
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);
98 return 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)
107 : approved_(false),
108 browser_(browser),
109 name_(name),
110 icon_(icon),
111 authuser_(authuser),
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 {
128 ItemList list;
130 for (const std::pair<std::string, Item>& entry : items_) {
131 if (entry.second.state == state)
132 list.push_back(entry.second);
135 return list;
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;
152 ParseManifests();
155 void BundleInstaller::CompleteInstall(content::WebContents* web_contents,
156 const base::Closure& callback) {
157 DCHECK(web_contents);
158 CHECK(approved_);
160 install_callback_ = callback;
162 if (!HasItemWithState(Item::STATE_PENDING)) {
163 install_callback_.Run();
164 return;
167 // Start each WebstoreInstaller.
168 for (const std::pair<std::string, Item>& entry : items_) {
169 if (entry.second.state != Item::STATE_PENDING)
170 continue;
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(
176 profile_,
177 entry.first,
178 make_scoped_ptr(parsed_manifests_[entry.first]->DeepCopy()),
179 true));
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(
187 profile_,
188 this,
189 web_contents,
190 entry.first,
191 approval.Pass(),
192 WebstoreInstaller::INSTALL_SOURCE_OTHER);
193 installer->Start();
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);
207 if (total == 0)
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_));
223 } else {
224 return l10n_util::GetStringFUTF16(kHeadingIdsInstallPrompt[index],
225 base::UTF8ToUTF16(name_));
227 } else {
228 return l10n_util::GetStringUTF16(kHeadingIdsInstalledBubble[index]);
232 void BundleInstaller::ParseManifests() {
233 if (items_.empty()) {
234 approval_callback_.Run(APPROVAL_ERROR);
235 return;
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,
244 context_getter);
245 helper->Start();
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())
252 return;
254 ShowPrompt();
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);
261 return;
264 scoped_refptr<PermissionSet> permissions;
265 for (size_t i = 0; i < dummy_extensions_.size(); ++i) {
266 permissions = PermissionSet::CreateUnion(
267 permissions.get(),
268 dummy_extensions_[i]->permissions_data()->active_permissions().get());
271 if (g_auto_approve_for_test == PROCEED) {
272 InstallUIProceed();
273 } else if (g_auto_approve_for_test == ABORT) {
274 InstallUIAbort(true);
275 } else {
276 Browser* browser = browser_;
277 if (!browser) {
278 // The browser that we got initially could have gone away during our
279 // thread hopping.
280 browser = chrome::FindLastActiveWithProfile(profile_, host_desktop_type_);
282 content::WebContents* web_contents = NULL;
283 if (browser)
284 web_contents = browser->tab_strip_model()->GetActiveWebContents();
285 install_ui_.reset(new ExtensionInstallPrompt(web_contents));
286 if (delegated_username_.empty()) {
287 install_ui_->ConfirmBundleInstall(this, &icon_, permissions.get());
288 } else {
289 install_ui_->ConfirmPermissionsForDelegatedBundleInstall(
290 this, delegated_username_, &icon_, permissions.get());
295 void BundleInstaller::ShowInstalledBubbleIfDone() {
296 // We're ready to show the installed bubble when no items are pending.
297 if (HasItemWithState(Item::STATE_PENDING))
298 return;
300 if (browser_)
301 ShowInstalledBubble(this, browser_);
303 install_callback_.Run();
306 void BundleInstaller::OnWebstoreParseSuccess(
307 const std::string& id,
308 const SkBitmap& icon,
309 base::DictionaryValue* manifest) {
310 items_[id].icon = icon;
311 dummy_extensions_.push_back(
312 CreateDummyExtension(items_[id], *manifest, profile_));
313 parsed_manifests_[id] = linked_ptr<base::DictionaryValue>(manifest);
315 ShowPromptIfDoneParsing();
318 void BundleInstaller::OnWebstoreParseFailure(
319 const std::string& id,
320 WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code,
321 const std::string& error_message) {
322 items_[id].state = Item::STATE_FAILED;
324 ShowPromptIfDoneParsing();
327 void BundleInstaller::InstallUIProceed() {
328 approved_ = true;
329 approval_callback_.Run(APPROVED);
332 void BundleInstaller::InstallUIAbort(bool user_initiated) {
333 for (std::pair<const std::string, Item>& entry : items_)
334 entry.second.state = Item::STATE_FAILED;
336 approval_callback_.Run(user_initiated ? USER_CANCELED : APPROVAL_ERROR);
339 void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) {
340 items_[id].state = Item::STATE_INSTALLED;
342 ShowInstalledBubbleIfDone();
345 void BundleInstaller::OnExtensionInstallFailure(
346 const std::string& id,
347 const std::string& error,
348 WebstoreInstaller::FailureReason reason) {
349 items_[id].state = Item::STATE_FAILED;
351 ExtensionList::iterator i = std::find_if(
352 dummy_extensions_.begin(), dummy_extensions_.end(),
353 [&id] (const scoped_refptr<const Extension>& ext) {
354 return ext->id() == id;
356 CHECK(dummy_extensions_.end() != i);
357 dummy_extensions_.erase(i);
359 ShowInstalledBubbleIfDone();
362 void BundleInstaller::OnBrowserRemoved(Browser* browser) {
363 if (browser_ == browser)
364 browser_ = nullptr;
367 } // namespace extensions