• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/extensions/bundle_installer.h"
6 
7 #include <algorithm>
8 #include <string>
9 #include <vector>
10 
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/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_finder.h"
19 #include "chrome/browser/ui/browser_list.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "content/public/browser/web_contents.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/permissions/permission_set.h"
25 #include "extensions/common/permissions/permissions_data.h"
26 #include "grit/generated_resources.h"
27 #include "ui/base/l10n/l10n_util.h"
28 
29 namespace extensions {
30 
31 namespace {
32 
33 enum AutoApproveForTest {
34   DO_NOT_SKIP = 0,
35   PROCEED,
36   ABORT
37 };
38 
39 AutoApproveForTest g_auto_approve_for_test = DO_NOT_SKIP;
40 
41 // Creates a dummy extension and sets the manifest's name to the item's
42 // localized name.
CreateDummyExtension(const BundleInstaller::Item & item,base::DictionaryValue * manifest)43 scoped_refptr<Extension> CreateDummyExtension(const BundleInstaller::Item& item,
44                                               base::DictionaryValue* manifest) {
45   // We require localized names so we can have nice error messages when we can't
46   // parse an extension manifest.
47   CHECK(!item.localized_name.empty());
48 
49   std::string error;
50   return Extension::Create(base::FilePath(),
51                            Manifest::INTERNAL,
52                            *manifest,
53                            Extension::NO_FLAGS,
54                            item.id,
55                            &error);
56 }
57 
IsAppPredicate(scoped_refptr<const Extension> extension)58 bool IsAppPredicate(scoped_refptr<const Extension> extension) {
59   return extension->is_app();
60 }
61 
62 struct MatchIdFunctor {
MatchIdFunctorextensions::__anon4fe608090111::MatchIdFunctor63   explicit MatchIdFunctor(const std::string& id) : id(id) {}
operator ()extensions::__anon4fe608090111::MatchIdFunctor64   bool operator()(scoped_refptr<const Extension> extension) {
65     return extension->id() == id;
66   }
67   std::string id;
68 };
69 
70 // Holds the message IDs for BundleInstaller::GetHeadingTextFor.
71 const int kHeadingIds[3][4] = {
72   {
73     0,
74     IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSIONS,
75     IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_APPS,
76     IDS_EXTENSION_BUNDLE_INSTALL_PROMPT_HEADING_EXTENSION_APPS
77   },
78   {
79     0,
80     IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSIONS,
81     IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_APPS,
82     IDS_EXTENSION_BUNDLE_INSTALLED_HEADING_EXTENSION_APPS
83   }
84 };
85 
86 }  // namespace
87 
88 // static
SetAutoApproveForTesting(bool auto_approve)89 void BundleInstaller::SetAutoApproveForTesting(bool auto_approve) {
90   CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType));
91   g_auto_approve_for_test = auto_approve ? PROCEED : ABORT;
92 }
93 
Item()94 BundleInstaller::Item::Item() : state(STATE_PENDING) {}
95 
GetNameForDisplay()96 base::string16 BundleInstaller::Item::GetNameForDisplay() {
97   base::string16 name = base::UTF8ToUTF16(localized_name);
98   base::i18n::AdjustStringForLocaleDirection(&name);
99   return l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE, name);
100 }
101 
BundleInstaller(Browser * browser,const BundleInstaller::ItemList & items)102 BundleInstaller::BundleInstaller(Browser* browser,
103                                  const BundleInstaller::ItemList& items)
104     : approved_(false),
105       browser_(browser),
106       host_desktop_type_(browser->host_desktop_type()),
107       profile_(browser->profile()),
108       delegate_(NULL) {
109   BrowserList::AddObserver(this);
110   for (size_t i = 0; i < items.size(); ++i) {
111     items_[items[i].id] = items[i];
112     items_[items[i].id].state = Item::STATE_PENDING;
113   }
114 }
115 
GetItemsWithState(Item::State state) const116 BundleInstaller::ItemList BundleInstaller::GetItemsWithState(
117     Item::State state) const {
118   ItemList list;
119 
120   for (ItemMap::const_iterator i = items_.begin(); i != items_.end(); ++i) {
121     if (i->second.state == state)
122       list.push_back(i->second);
123   }
124 
125   return list;
126 }
127 
PromptForApproval(Delegate * delegate)128 void BundleInstaller::PromptForApproval(Delegate* delegate) {
129   delegate_ = delegate;
130 
131   AddRef();  // Balanced in ReportApproved() and ReportCanceled().
132 
133   ParseManifests();
134 }
135 
CompleteInstall(content::WebContents * web_contents,Delegate * delegate)136 void BundleInstaller::CompleteInstall(content::WebContents* web_contents,
137                                       Delegate* delegate) {
138   CHECK(approved_);
139 
140   delegate_ = delegate;
141 
142   AddRef();  // Balanced in ReportComplete();
143 
144   if (GetItemsWithState(Item::STATE_PENDING).empty()) {
145     ReportComplete();
146     return;
147   }
148 
149   // Start each WebstoreInstaller.
150   for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) {
151     if (i->second.state != Item::STATE_PENDING)
152       continue;
153 
154     // Since we've already confirmed the permissions, create an approval that
155     // lets CrxInstaller bypass the prompt.
156     scoped_ptr<WebstoreInstaller::Approval> approval(
157         WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
158             profile_,
159             i->first,
160             scoped_ptr<base::DictionaryValue>(
161                 parsed_manifests_[i->first]->DeepCopy()), true));
162     approval->use_app_installed_bubble = false;
163     approval->skip_post_install_ui = true;
164 
165     scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
166         profile_,
167         this,
168         web_contents,
169         i->first,
170         approval.Pass(),
171         WebstoreInstaller::INSTALL_SOURCE_OTHER);
172     installer->Start();
173   }
174 }
175 
GetHeadingTextFor(Item::State state) const176 base::string16 BundleInstaller::GetHeadingTextFor(Item::State state) const {
177   // For STATE_FAILED, we can't tell if the items were apps or extensions
178   // so we always show the same message.
179   if (state == Item::STATE_FAILED) {
180     if (GetItemsWithState(state).size())
181       return l10n_util::GetStringUTF16(IDS_EXTENSION_BUNDLE_ERROR_HEADING);
182     return base::string16();
183   }
184 
185   size_t total = GetItemsWithState(state).size();
186   size_t apps = std::count_if(
187       dummy_extensions_.begin(), dummy_extensions_.end(), &IsAppPredicate);
188 
189   bool has_apps = apps > 0;
190   bool has_extensions = apps < total;
191   size_t index = (has_extensions << 0) + (has_apps << 1);
192 
193   CHECK_LT(static_cast<size_t>(state), arraysize(kHeadingIds));
194   CHECK_LT(index, arraysize(kHeadingIds[state]));
195 
196   int msg_id = kHeadingIds[state][index];
197   if (!msg_id)
198     return base::string16();
199 
200   return l10n_util::GetStringUTF16(msg_id);
201 }
202 
~BundleInstaller()203 BundleInstaller::~BundleInstaller() {
204   BrowserList::RemoveObserver(this);
205 }
206 
ParseManifests()207 void BundleInstaller::ParseManifests() {
208   if (items_.empty()) {
209     ReportCanceled(false);
210     return;
211   }
212 
213   for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i) {
214     scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
215         this, i->first, i->second.manifest, std::string(), GURL(), NULL);
216     helper->Start();
217   }
218 }
219 
ReportApproved()220 void BundleInstaller::ReportApproved() {
221   if (delegate_)
222     delegate_->OnBundleInstallApproved();
223 
224   Release();  // Balanced in PromptForApproval().
225 }
226 
ReportCanceled(bool user_initiated)227 void BundleInstaller::ReportCanceled(bool user_initiated) {
228   if (delegate_)
229     delegate_->OnBundleInstallCanceled(user_initiated);
230 
231   Release();  // Balanced in PromptForApproval().
232 }
233 
ReportComplete()234 void BundleInstaller::ReportComplete() {
235   if (delegate_)
236     delegate_->OnBundleInstallCompleted();
237 
238   Release();  // Balanced in CompleteInstall().
239 }
240 
ShowPromptIfDoneParsing()241 void BundleInstaller::ShowPromptIfDoneParsing() {
242   // We don't prompt until all the manifests have been parsed.
243   ItemList pending_items = GetItemsWithState(Item::STATE_PENDING);
244   if (pending_items.size() != dummy_extensions_.size())
245     return;
246 
247   ShowPrompt();
248 }
249 
ShowPrompt()250 void BundleInstaller::ShowPrompt() {
251   // Abort if we couldn't create any Extensions out of the manifests.
252   if (dummy_extensions_.empty()) {
253     ReportCanceled(false);
254     return;
255   }
256 
257   scoped_refptr<PermissionSet> permissions;
258   for (size_t i = 0; i < dummy_extensions_.size(); ++i) {
259     permissions = PermissionSet::CreateUnion(
260         permissions.get(),
261         dummy_extensions_[i]->permissions_data()->active_permissions());
262   }
263 
264   if (g_auto_approve_for_test == PROCEED) {
265     InstallUIProceed();
266   } else if (g_auto_approve_for_test == ABORT) {
267     InstallUIAbort(true);
268   } else {
269     Browser* browser = browser_;
270     if (!browser) {
271       // The browser that we got initially could have gone away during our
272       // thread hopping.
273       browser = chrome::FindLastActiveWithProfile(profile_, host_desktop_type_);
274     }
275     content::WebContents* web_contents = NULL;
276     if (browser)
277       web_contents = browser->tab_strip_model()->GetActiveWebContents();
278     install_ui_.reset(new ExtensionInstallPrompt(web_contents));
279     install_ui_->ConfirmBundleInstall(this, permissions.get());
280   }
281 }
282 
ShowInstalledBubbleIfDone()283 void BundleInstaller::ShowInstalledBubbleIfDone() {
284   // We're ready to show the installed bubble when no items are pending.
285   if (!GetItemsWithState(Item::STATE_PENDING).empty())
286     return;
287 
288   if (browser_)
289     ShowInstalledBubble(this, browser_);
290 
291   ReportComplete();
292 }
293 
OnWebstoreParseSuccess(const std::string & id,const SkBitmap & icon,base::DictionaryValue * manifest)294 void BundleInstaller::OnWebstoreParseSuccess(
295     const std::string& id,
296     const SkBitmap& icon,
297     base::DictionaryValue* manifest) {
298   dummy_extensions_.push_back(CreateDummyExtension(items_[id], manifest));
299   parsed_manifests_[id] = linked_ptr<base::DictionaryValue>(manifest);
300 
301   ShowPromptIfDoneParsing();
302 }
303 
OnWebstoreParseFailure(const std::string & id,WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code,const std::string & error_message)304 void BundleInstaller::OnWebstoreParseFailure(
305     const std::string& id,
306     WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code,
307     const std::string& error_message) {
308   items_[id].state = Item::STATE_FAILED;
309 
310   ShowPromptIfDoneParsing();
311 }
312 
InstallUIProceed()313 void BundleInstaller::InstallUIProceed() {
314   approved_ = true;
315   ReportApproved();
316 }
317 
InstallUIAbort(bool user_initiated)318 void BundleInstaller::InstallUIAbort(bool user_initiated) {
319   for (ItemMap::iterator i = items_.begin(); i != items_.end(); ++i)
320     i->second.state = Item::STATE_FAILED;
321 
322   ReportCanceled(user_initiated);
323 }
324 
OnExtensionInstallSuccess(const std::string & id)325 void BundleInstaller::OnExtensionInstallSuccess(const std::string& id) {
326   items_[id].state = Item::STATE_INSTALLED;
327 
328   ShowInstalledBubbleIfDone();
329 }
330 
OnExtensionInstallFailure(const std::string & id,const std::string & error,WebstoreInstaller::FailureReason reason)331 void BundleInstaller::OnExtensionInstallFailure(
332     const std::string& id,
333     const std::string& error,
334     WebstoreInstaller::FailureReason reason) {
335   items_[id].state = Item::STATE_FAILED;
336 
337   ExtensionList::iterator i = std::find_if(
338       dummy_extensions_.begin(), dummy_extensions_.end(), MatchIdFunctor(id));
339   CHECK(dummy_extensions_.end() != i);
340   dummy_extensions_.erase(i);
341 
342   ShowInstalledBubbleIfDone();
343 }
344 
OnBrowserAdded(Browser * browser)345 void BundleInstaller::OnBrowserAdded(Browser* browser) {}
346 
OnBrowserRemoved(Browser * browser)347 void BundleInstaller::OnBrowserRemoved(Browser* browser) {
348   if (browser_ == browser)
349     browser_ = NULL;
350 }
351 
OnBrowserSetLastActive(Browser * browser)352 void BundleInstaller::OnBrowserSetLastActive(Browser* browser) {}
353 
354 }  // namespace extensions
355