Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(249)

Side by Side Diff: chrome/browser/extensions/external_install_ui.cc

Issue 309643007: Resubmit: Refactor external_install_ui (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/extensions/external_install_ui.h ('k') | chrome/chrome_browser_extensions.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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/external_install_ui.h"
6
7 #include <string>
8
9 #include "base/bind.h"
10 #include "base/lazy_instance.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/metrics/histogram.h"
15 #include "base/scoped_observer.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/app/chrome_command_ids.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/extensions/extension_install_prompt.h"
20 #include "chrome/browser/extensions/extension_install_ui.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
23 #include "chrome/browser/extensions/webstore_data_fetcher.h"
24 #include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_finder.h"
28 #include "chrome/browser/ui/global_error/global_error.h"
29 #include "chrome/browser/ui/global_error/global_error_service.h"
30 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
31 #include "chrome/common/extensions/manifest_url_handler.h"
32 #include "content/public/browser/notification_details.h"
33 #include "content/public/browser/notification_observer.h"
34 #include "content/public/browser/notification_registrar.h"
35 #include "content/public/browser/notification_source.h"
36 #include "extensions/browser/extension_registry.h"
37 #include "extensions/browser/extension_registry_observer.h"
38 #include "extensions/common/constants.h"
39 #include "grit/generated_resources.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/gfx/image/image.h"
42 #include "ui/gfx/image/image_skia_operations.h"
43
44 namespace extensions {
45
46 namespace {
47
48 // Whether the external extension can use the streamlined bubble install flow.
49 bool UseBubbleInstall(const Extension* extension, bool is_new_profile) {
50 return ManifestURL::UpdatesFromGallery(extension) && !is_new_profile;
51 }
52
53 } // namespace
54
55 static const int kMenuCommandId = IDC_EXTERNAL_EXTENSION_ALERT;
56
57 class ExternalInstallGlobalError;
58
59 namespace extensions {
60 class ExtensionRegistry;
61 }
62
63 // This class is refcounted to stay alive while we try and pull webstore data.
64 class ExternalInstallDialogDelegate
65 : public ExtensionInstallPrompt::Delegate,
66 public WebstoreDataFetcherDelegate,
67 public content::NotificationObserver,
68 public base::RefCountedThreadSafe<ExternalInstallDialogDelegate> {
69 public:
70 ExternalInstallDialogDelegate(Browser* browser,
71 ExtensionService* service,
72 const Extension* extension,
73 bool use_global_error);
74
75 Browser* browser() { return browser_; }
76
77 private:
78 friend class base::RefCountedThreadSafe<ExternalInstallDialogDelegate>;
79 friend class ExternalInstallGlobalError;
80
81 virtual ~ExternalInstallDialogDelegate();
82
83 // ExtensionInstallPrompt::Delegate:
84 virtual void InstallUIProceed() OVERRIDE;
85 virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
86
87 // WebstoreDataFetcherDelegate:
88 virtual void OnWebstoreRequestFailure() OVERRIDE;
89 virtual void OnWebstoreResponseParseSuccess(
90 scoped_ptr<base::DictionaryValue> webstore_data) OVERRIDE;
91 virtual void OnWebstoreResponseParseFailure(
92 const std::string& error) OVERRIDE;
93
94 // content::NotificationObserver:
95 virtual void Observe(int type,
96 const content::NotificationSource& source,
97 const content::NotificationDetails& details) OVERRIDE;
98
99 // Show the install dialog to the user.
100 void ShowInstallUI();
101
102 // The UI for showing the install dialog when enabling.
103 scoped_ptr<ExtensionInstallPrompt> install_ui_;
104 scoped_ptr<ExtensionInstallPrompt::Prompt> prompt_;
105
106 Browser* browser_;
107 base::WeakPtr<ExtensionService> service_weak_;
108 scoped_ptr<WebstoreDataFetcher> webstore_data_fetcher_;
109 content::NotificationRegistrar registrar_;
110 std::string extension_id_;
111 bool use_global_error_;
112
113 DISALLOW_COPY_AND_ASSIGN(ExternalInstallDialogDelegate);
114 };
115
116 // Only shows a menu item, no bubble. Clicking the menu item shows
117 // an external install dialog.
118 class ExternalInstallMenuAlert : public GlobalErrorWithStandardBubble,
119 public content::NotificationObserver,
120 public ExtensionRegistryObserver {
121 public:
122 ExternalInstallMenuAlert(ExtensionService* service,
123 const Extension* extension);
124 virtual ~ExternalInstallMenuAlert();
125
126 // GlobalError implementation.
127 virtual Severity GetSeverity() OVERRIDE;
128 virtual bool HasMenuItem() OVERRIDE;
129 virtual int MenuItemCommandID() OVERRIDE;
130 virtual base::string16 MenuItemLabel() OVERRIDE;
131 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
132 virtual bool HasBubbleView() OVERRIDE;
133 virtual base::string16 GetBubbleViewTitle() OVERRIDE;
134 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
135 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
136 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
137 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
138 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
139 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
140
141 protected:
142 ExtensionService* service_;
143 const Extension* extension_;
144
145 private:
146 // Delete this instance after cleaning jobs.
147 void Clean();
148
149 // content::NotificationObserver implementation.
150 virtual void Observe(int type,
151 const content::NotificationSource& source,
152 const content::NotificationDetails& details) OVERRIDE;
153
154 // ExtensionRegistryObserver implementation.
155 virtual void OnExtensionLoaded(content::BrowserContext* browser_context,
156 const Extension* extension) OVERRIDE;
157
158 content::NotificationRegistrar registrar_;
159
160 // Listen to extension load notifications.
161 ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
162 extension_registry_observer_;
163
164 DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert);
165 };
166
167 // Shows a menu item and a global error bubble, replacing the install dialog.
168 class ExternalInstallGlobalError : public ExternalInstallMenuAlert {
169 public:
170 ExternalInstallGlobalError(ExtensionService* service,
171 const Extension* extension,
172 ExternalInstallDialogDelegate* delegate,
173 const ExtensionInstallPrompt::Prompt& prompt);
174 virtual ~ExternalInstallGlobalError();
175
176 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
177 virtual bool HasBubbleView() OVERRIDE;
178 virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
179 virtual base::string16 GetBubbleViewTitle() OVERRIDE;
180 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
181 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
182 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
183 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
184 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
185 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
186
187 protected:
188 // Ref-counted, but needs to be disposed of if we are dismissed without
189 // having been clicked (perhaps because the user enabled the extension
190 // manually).
191 ExternalInstallDialogDelegate* delegate_;
192 const ExtensionInstallPrompt::Prompt* prompt_;
193
194 private:
195 DISALLOW_COPY_AND_ASSIGN(ExternalInstallGlobalError);
196 };
197
198 static void CreateExternalInstallGlobalError(
199 base::WeakPtr<ExtensionService> service,
200 const std::string& extension_id,
201 const ExtensionInstallPrompt::ShowParams& show_params,
202 ExtensionInstallPrompt::Delegate* prompt_delegate,
203 const ExtensionInstallPrompt::Prompt& prompt) {
204 if (!service.get())
205 return;
206 const Extension* extension = service->GetInstalledExtension(extension_id);
207 if (!extension)
208 return;
209 GlobalErrorService* error_service =
210 GlobalErrorServiceFactory::GetForProfile(service->profile());
211 if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
212 return;
213
214 ExternalInstallDialogDelegate* delegate =
215 static_cast<ExternalInstallDialogDelegate*>(prompt_delegate);
216 ExternalInstallGlobalError* error_bubble = new ExternalInstallGlobalError(
217 service.get(), extension, delegate, prompt);
218 error_service->AddGlobalError(error_bubble);
219 // Show bubble immediately if possible.
220 if (delegate->browser())
221 error_bubble->ShowBubbleView(delegate->browser());
222 }
223
224 static void ShowExternalInstallDialog(
225 ExtensionService* service,
226 Browser* browser,
227 const Extension* extension) {
228 // This object manages its own lifetime.
229 new ExternalInstallDialogDelegate(browser, service, extension, false);
230 }
231
232 // ExternalInstallDialogDelegate --------------------------------------------
233
234 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
235 Browser* browser,
236 ExtensionService* service,
237 const Extension* extension,
238 bool use_global_error)
239 : browser_(browser),
240 service_weak_(service->AsWeakPtr()),
241 extension_id_(extension->id()),
242 use_global_error_(use_global_error) {
243 AddRef(); // Balanced in Proceed or Abort.
244
245 prompt_.reset(new ExtensionInstallPrompt::Prompt(
246 ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT));
247
248 // If we don't have a browser, we can't go to the webstore to fetch data.
249 // This should only happen in tests.
250 if (!browser) {
251 ShowInstallUI();
252 return;
253 }
254
255 // Make sure to be notified if the owning profile is destroyed.
256 registrar_.Add(this,
257 chrome::NOTIFICATION_PROFILE_DESTROYED,
258 content::Source<Profile>(browser->profile()));
259
260 webstore_data_fetcher_.reset(new WebstoreDataFetcher(
261 this,
262 browser->profile()->GetRequestContext(),
263 GURL::EmptyGURL(),
264 extension->id()));
265 webstore_data_fetcher_->Start();
266 }
267
268 void ExternalInstallDialogDelegate::OnWebstoreRequestFailure() {
269 ShowInstallUI();
270 }
271
272 void ExternalInstallDialogDelegate::OnWebstoreResponseParseSuccess(
273 scoped_ptr<base::DictionaryValue> webstore_data) {
274 std::string localized_user_count;
275 double average_rating;
276 int rating_count;
277 if (!webstore_data->GetString(kUsersKey, &localized_user_count) ||
278 !webstore_data->GetDouble(kAverageRatingKey, &average_rating) ||
279 !webstore_data->GetInteger(kRatingCountKey, &rating_count)) {
280 // If we don't get a valid webstore response, short circuit, and continue
281 // to show a prompt without webstore data.
282 ShowInstallUI();
283 return;
284 }
285
286 bool show_user_count = true;
287 webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
288
289 prompt_->SetWebstoreData(localized_user_count,
290 show_user_count,
291 average_rating,
292 rating_count);
293
294 ShowInstallUI();
295 }
296
297 void ExternalInstallDialogDelegate::OnWebstoreResponseParseFailure(
298 const std::string& error) {
299 ShowInstallUI();
300 }
301
302 void ExternalInstallDialogDelegate::Observe(
303 int type,
304 const content::NotificationSource& source,
305 const content::NotificationDetails& details) {
306 DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
307 // If the owning profile is destroyed, we need to abort so that we don't leak.
308 InstallUIAbort(false); // Not user initiated.
309 }
310
311 void ExternalInstallDialogDelegate::ShowInstallUI() {
312 const Extension* extension = NULL;
313 if (!service_weak_.get() ||
314 !(extension = service_weak_->GetInstalledExtension(extension_id_))) {
315 return;
316 }
317 install_ui_.reset(
318 ExtensionInstallUI::CreateInstallPromptWithBrowser(browser_));
319
320 const ExtensionInstallPrompt::ShowDialogCallback callback =
321 use_global_error_ ?
322 base::Bind(&CreateExternalInstallGlobalError,
323 service_weak_,
324 extension_id_) :
325 ExtensionInstallPrompt::GetDefaultShowDialogCallback();
326
327 install_ui_->ConfirmExternalInstall(this, extension, callback, *prompt_);
328 }
329
330 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
331 }
332
333 void ExternalInstallDialogDelegate::InstallUIProceed() {
334 const Extension* extension = NULL;
335 if (service_weak_.get() &&
336 (extension = service_weak_->GetInstalledExtension(extension_id_))) {
337 service_weak_->GrantPermissionsAndEnableExtension(extension);
338 }
339 Release();
340 }
341
342 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated) {
343 const Extension* extension = NULL;
344
345 // Uninstall the extension if the abort was user initiated (and not, e.g., the
346 // result of the window closing).
347 // Otherwise, the extension will remain installed, but unacknowledged, so it
348 // will be prompted again.
349 if (user_initiated &&
350 service_weak_.get() &&
351 (extension = service_weak_->GetInstalledExtension(extension_id_))) {
352 service_weak_->UninstallExtension(extension_id_, false, NULL);
353 }
354 Release();
355 }
356
357 // ExternalInstallMenuAlert -------------------------------------------------
358
359 ExternalInstallMenuAlert::ExternalInstallMenuAlert(ExtensionService* service,
360 const Extension* extension)
361 : service_(service),
362 extension_(extension),
363 extension_registry_observer_(this) {
364 extension_registry_observer_.Add(ExtensionRegistry::Get(service->profile()));
365 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED,
366 content::Source<Profile>(service->profile()));
367 }
368
369 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
370 }
371
372 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
373 return SEVERITY_LOW;
374 }
375
376 bool ExternalInstallMenuAlert::HasMenuItem() {
377 return true;
378 }
379
380 int ExternalInstallMenuAlert::MenuItemCommandID() {
381 return kMenuCommandId;
382 }
383
384 base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
385 int id = -1;
386 if (extension_->is_app())
387 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
388 else if (extension_->is_theme())
389 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
390 else
391 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
392 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name()));
393 }
394
395 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
396 ShowExternalInstallDialog(service_, browser, extension_);
397 }
398
399 bool ExternalInstallMenuAlert::HasBubbleView() {
400 return false;
401 }
402 base::string16 ExternalInstallMenuAlert::GetBubbleViewTitle() {
403 return base::string16();
404 }
405
406 std::vector<base::string16> ExternalInstallMenuAlert::GetBubbleViewMessages() {
407 return std::vector<base::string16>();
408 }
409
410 base::string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
411 return base::string16();
412 }
413
414 base::string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
415 return base::string16();
416 }
417
418 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) {
419 NOTREACHED();
420 }
421
422 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
423 Browser* browser) {
424 NOTREACHED();
425 }
426
427 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
428 Browser* browser) {
429 NOTREACHED();
430 }
431
432 void ExternalInstallMenuAlert::OnExtensionLoaded(
433 content::BrowserContext* browser_context,
434 const Extension* extension) {
435 if (extension == extension_)
436 Clean();
437 }
438
439 void ExternalInstallMenuAlert::Observe(
440 int type,
441 const content::NotificationSource& source,
442 const content::NotificationDetails& details) {
443 // The error is invalidated if the extension has been loaded or removed.
444 DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_REMOVED);
445 const Extension* extension = content::Details<const Extension>(details).ptr();
446 if (extension == extension_)
447 Clean();
448 }
449
450 void ExternalInstallMenuAlert::Clean() {
451 GlobalErrorService* error_service =
452 GlobalErrorServiceFactory::GetForProfile(service_->profile());
453 error_service->RemoveGlobalError(this);
454 service_->AcknowledgeExternalExtension(extension_->id());
455 delete this;
456 }
457
458 // ExternalInstallGlobalError -----------------------------------------------
459
460 ExternalInstallGlobalError::ExternalInstallGlobalError(
461 ExtensionService* service,
462 const Extension* extension,
463 ExternalInstallDialogDelegate* delegate,
464 const ExtensionInstallPrompt::Prompt& prompt)
465 : ExternalInstallMenuAlert(service, extension),
466 delegate_(delegate),
467 prompt_(&prompt) {
468 }
469
470 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
471 if (delegate_)
472 delegate_->Release();
473 }
474
475 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) {
476 ShowBubbleView(browser);
477 }
478
479 bool ExternalInstallGlobalError::HasBubbleView() {
480 return true;
481 }
482
483 gfx::Image ExternalInstallGlobalError::GetBubbleViewIcon() {
484 if (prompt_->icon().IsEmpty())
485 return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
486 // Scale icon to a reasonable size.
487 return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
488 *prompt_->icon().ToImageSkia(),
489 skia::ImageOperations::RESIZE_BEST,
490 gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
491 extension_misc::EXTENSION_ICON_SMALL)));
492 }
493
494 base::string16 ExternalInstallGlobalError::GetBubbleViewTitle() {
495 return prompt_->GetDialogTitle();
496 }
497
498 std::vector<base::string16>
499 ExternalInstallGlobalError::GetBubbleViewMessages() {
500 std::vector<base::string16> messages;
501 messages.push_back(prompt_->GetHeading());
502 if (prompt_->GetPermissionCount()) {
503 messages.push_back(prompt_->GetPermissionsHeading());
504 for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) {
505 messages.push_back(l10n_util::GetStringFUTF16(
506 IDS_EXTENSION_PERMISSION_LINE,
507 prompt_->GetPermission(i)));
508 }
509 }
510 // TODO(yoz): OAuth issue advice?
511 return messages;
512 }
513
514 base::string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
515 return prompt_->GetAcceptButtonLabel();
516 }
517
518 base::string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
519 return prompt_->GetAbortButtonLabel();
520 }
521
522 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) {
523 }
524
525 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
526 Browser* browser) {
527 ExternalInstallDialogDelegate* delegate = delegate_;
528 delegate_ = NULL;
529 delegate->InstallUIProceed();
530 }
531
532 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
533 Browser* browser) {
534 ExternalInstallDialogDelegate* delegate = delegate_;
535 delegate_ = NULL;
536 delegate->InstallUIAbort(true);
537 }
538
539 // Public interface ---------------------------------------------------------
540
541 void AddExternalInstallError(ExtensionService* service,
542 const Extension* extension,
543 bool is_new_profile) {
544 GlobalErrorService* error_service =
545 GlobalErrorServiceFactory::GetForProfile(service->profile());
546 if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
547 return;
548
549 if (UseBubbleInstall(extension, is_new_profile)) {
550 Browser* browser = NULL;
551 #if !defined(OS_ANDROID)
552 browser = chrome::FindTabbedBrowser(service->profile(),
553 true,
554 chrome::GetActiveDesktop());
555 #endif
556 new ExternalInstallDialogDelegate(browser, service, extension, true);
557 } else {
558 error_service->AddGlobalError(
559 new ExternalInstallMenuAlert(service, extension));
560 }
561 }
562
563 void RemoveExternalInstallError(ExtensionService* service) {
564 GlobalErrorService* error_service =
565 GlobalErrorServiceFactory::GetForProfile(service->profile());
566 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
567 kMenuCommandId);
568 if (error) {
569 error_service->RemoveGlobalError(error);
570 delete error;
571 }
572 }
573
574 bool HasExternalInstallError(ExtensionService* service) {
575 GlobalErrorService* error_service =
576 GlobalErrorServiceFactory::GetForProfile(service->profile());
577 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
578 kMenuCommandId);
579 return !!error;
580 }
581
582 bool HasExternalInstallBubble(ExtensionService* service) {
583 GlobalErrorService* error_service =
584 GlobalErrorServiceFactory::GetForProfile(service->profile());
585 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
586 kMenuCommandId);
587 return error && error->HasBubbleView();
588 }
589
590 } // namespace extensions
OLDNEW
« no previous file with comments | « chrome/browser/extensions/external_install_ui.h ('k') | chrome/chrome_browser_extensions.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698