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