EME test page application.
[chromium-blink-merge.git] / chrome / browser / extensions / external_install_ui.cc
blobdc0abc4c292baa052c213a15bf6a57052c555db0
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/external_install_ui.h"
7 #include <string>
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 "grit/generated_resources.h"
39 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/gfx/image/image.h"
41 #include "ui/gfx/image/image_skia_operations.h"
43 namespace extensions {
45 namespace {
47 // Whether the external extension can use the streamlined bubble install flow.
48 bool UseBubbleInstall(const Extension* extension, bool is_new_profile) {
49 return ManifestURL::UpdatesFromGallery(extension) && !is_new_profile;
52 } // namespace
54 static const int kMenuCommandId = IDC_EXTERNAL_EXTENSION_ALERT;
56 class ExternalInstallGlobalError;
58 namespace extensions {
59 class ExtensionRegistry;
62 // This class is refcounted to stay alive while we try and pull webstore data.
63 class ExternalInstallDialogDelegate
64 : public ExtensionInstallPrompt::Delegate,
65 public WebstoreDataFetcherDelegate,
66 public content::NotificationObserver,
67 public base::RefCountedThreadSafe<ExternalInstallDialogDelegate> {
68 public:
69 ExternalInstallDialogDelegate(Browser* browser,
70 ExtensionService* service,
71 const Extension* extension,
72 bool use_global_error);
74 Browser* browser() { return browser_; }
76 private:
77 friend class base::RefCountedThreadSafe<ExternalInstallDialogDelegate>;
78 friend class ExternalInstallGlobalError;
80 virtual ~ExternalInstallDialogDelegate();
82 // ExtensionInstallPrompt::Delegate:
83 virtual void InstallUIProceed() OVERRIDE;
84 virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
86 // WebstoreDataFetcherDelegate:
87 virtual void OnWebstoreRequestFailure() OVERRIDE;
88 virtual void OnWebstoreResponseParseSuccess(
89 scoped_ptr<base::DictionaryValue> webstore_data) OVERRIDE;
90 virtual void OnWebstoreResponseParseFailure(
91 const std::string& error) OVERRIDE;
93 // content::NotificationObserver:
94 virtual void Observe(int type,
95 const content::NotificationSource& source,
96 const content::NotificationDetails& details) OVERRIDE;
98 // Show the install dialog to the user.
99 void ShowInstallUI();
101 // The UI for showing the install dialog when enabling.
102 scoped_ptr<ExtensionInstallPrompt> install_ui_;
103 scoped_ptr<ExtensionInstallPrompt::Prompt> prompt_;
105 Browser* browser_;
106 base::WeakPtr<ExtensionService> service_weak_;
107 scoped_ptr<WebstoreDataFetcher> webstore_data_fetcher_;
108 content::NotificationRegistrar registrar_;
109 std::string extension_id_;
110 bool use_global_error_;
112 DISALLOW_COPY_AND_ASSIGN(ExternalInstallDialogDelegate);
115 // Only shows a menu item, no bubble. Clicking the menu item shows
116 // an external install dialog.
117 class ExternalInstallMenuAlert : public GlobalErrorWithStandardBubble,
118 public content::NotificationObserver,
119 public ExtensionRegistryObserver {
120 public:
121 ExternalInstallMenuAlert(ExtensionService* service,
122 const Extension* extension);
123 virtual ~ExternalInstallMenuAlert();
125 // GlobalError implementation.
126 virtual Severity GetSeverity() OVERRIDE;
127 virtual bool HasMenuItem() OVERRIDE;
128 virtual int MenuItemCommandID() OVERRIDE;
129 virtual base::string16 MenuItemLabel() OVERRIDE;
130 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
131 virtual bool HasBubbleView() OVERRIDE;
132 virtual base::string16 GetBubbleViewTitle() OVERRIDE;
133 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
134 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
135 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
136 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
137 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
138 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
140 protected:
141 ExtensionService* service_;
142 const Extension* extension_;
144 private:
145 // Delete this instance after cleaning jobs.
146 void Clean();
148 // content::NotificationObserver implementation.
149 virtual void Observe(int type,
150 const content::NotificationSource& source,
151 const content::NotificationDetails& details) OVERRIDE;
153 // ExtensionRegistryObserver implementation.
154 virtual void OnExtensionLoaded(content::BrowserContext* browser_context,
155 const Extension* extension) OVERRIDE;
157 content::NotificationRegistrar registrar_;
159 // Listen to extension load notifications.
160 ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
161 extension_registry_observer_;
163 DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert);
166 // Shows a menu item and a global error bubble, replacing the install dialog.
167 class ExternalInstallGlobalError : public ExternalInstallMenuAlert {
168 public:
169 ExternalInstallGlobalError(ExtensionService* service,
170 const Extension* extension,
171 ExternalInstallDialogDelegate* delegate,
172 const ExtensionInstallPrompt::Prompt& prompt);
173 virtual ~ExternalInstallGlobalError();
175 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
176 virtual bool HasBubbleView() OVERRIDE;
177 virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
178 virtual base::string16 GetBubbleViewTitle() OVERRIDE;
179 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
180 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
181 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
182 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
183 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
184 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
186 protected:
187 // Ref-counted, but needs to be disposed of if we are dismissed without
188 // having been clicked (perhaps because the user enabled the extension
189 // manually).
190 ExternalInstallDialogDelegate* delegate_;
191 const ExtensionInstallPrompt::Prompt* prompt_;
193 private:
194 DISALLOW_COPY_AND_ASSIGN(ExternalInstallGlobalError);
197 static void CreateExternalInstallGlobalError(
198 base::WeakPtr<ExtensionService> service,
199 const std::string& extension_id,
200 const ExtensionInstallPrompt::ShowParams& show_params,
201 ExtensionInstallPrompt::Delegate* prompt_delegate,
202 const ExtensionInstallPrompt::Prompt& prompt) {
203 if (!service.get())
204 return;
205 const Extension* extension = service->GetInstalledExtension(extension_id);
206 if (!extension)
207 return;
208 GlobalErrorService* error_service =
209 GlobalErrorServiceFactory::GetForProfile(service->profile());
210 if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
211 return;
213 ExternalInstallDialogDelegate* delegate =
214 static_cast<ExternalInstallDialogDelegate*>(prompt_delegate);
215 ExternalInstallGlobalError* error_bubble = new ExternalInstallGlobalError(
216 service.get(), extension, delegate, prompt);
217 error_service->AddGlobalError(error_bubble);
218 // Show bubble immediately if possible.
219 if (delegate->browser())
220 error_bubble->ShowBubbleView(delegate->browser());
223 static void ShowExternalInstallDialog(
224 ExtensionService* service,
225 Browser* browser,
226 const Extension* extension) {
227 // This object manages its own lifetime.
228 new ExternalInstallDialogDelegate(browser, service, extension, false);
231 // ExternalInstallDialogDelegate --------------------------------------------
233 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
234 Browser* browser,
235 ExtensionService* service,
236 const Extension* extension,
237 bool use_global_error)
238 : browser_(browser),
239 service_weak_(service->AsWeakPtr()),
240 extension_id_(extension->id()),
241 use_global_error_(use_global_error) {
242 AddRef(); // Balanced in Proceed or Abort.
244 prompt_.reset(new ExtensionInstallPrompt::Prompt(
245 ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT));
247 // If we don't have a browser, we can't go to the webstore to fetch data.
248 // This should only happen in tests.
249 if (!browser) {
250 ShowInstallUI();
251 return;
254 // Make sure to be notified if the owning profile is destroyed.
255 registrar_.Add(this,
256 chrome::NOTIFICATION_PROFILE_DESTROYED,
257 content::Source<Profile>(browser->profile()));
259 webstore_data_fetcher_.reset(new WebstoreDataFetcher(
260 this,
261 browser->profile()->GetRequestContext(),
262 GURL::EmptyGURL(),
263 extension->id()));
264 webstore_data_fetcher_->Start();
267 void ExternalInstallDialogDelegate::OnWebstoreRequestFailure() {
268 ShowInstallUI();
271 void ExternalInstallDialogDelegate::OnWebstoreResponseParseSuccess(
272 scoped_ptr<base::DictionaryValue> webstore_data) {
273 std::string localized_user_count;
274 double average_rating;
275 int rating_count;
276 if (!webstore_data->GetString(kUsersKey, &localized_user_count) ||
277 !webstore_data->GetDouble(kAverageRatingKey, &average_rating) ||
278 !webstore_data->GetInteger(kRatingCountKey, &rating_count)) {
279 // If we don't get a valid webstore response, short circuit, and continue
280 // to show a prompt without webstore data.
281 ShowInstallUI();
282 return;
285 bool show_user_count = true;
286 webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
288 prompt_->SetWebstoreData(localized_user_count,
289 show_user_count,
290 average_rating,
291 rating_count);
293 ShowInstallUI();
296 void ExternalInstallDialogDelegate::OnWebstoreResponseParseFailure(
297 const std::string& error) {
298 ShowInstallUI();
301 void ExternalInstallDialogDelegate::Observe(
302 int type,
303 const content::NotificationSource& source,
304 const content::NotificationDetails& details) {
305 DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
306 // If the owning profile is destroyed, we need to abort so that we don't leak.
307 InstallUIAbort(false); // Not user initiated.
310 void ExternalInstallDialogDelegate::ShowInstallUI() {
311 const Extension* extension = NULL;
312 if (!service_weak_.get() ||
313 !(extension = service_weak_->GetInstalledExtension(extension_id_))) {
314 return;
316 install_ui_.reset(
317 ExtensionInstallUI::CreateInstallPromptWithBrowser(browser_));
319 const ExtensionInstallPrompt::ShowDialogCallback callback =
320 use_global_error_ ?
321 base::Bind(&CreateExternalInstallGlobalError,
322 service_weak_,
323 extension_id_) :
324 ExtensionInstallPrompt::GetDefaultShowDialogCallback();
326 install_ui_->ConfirmExternalInstall(this, extension, callback, *prompt_);
329 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
332 void ExternalInstallDialogDelegate::InstallUIProceed() {
333 const Extension* extension = NULL;
334 if (service_weak_.get() &&
335 (extension = service_weak_->GetInstalledExtension(extension_id_))) {
336 service_weak_->GrantPermissionsAndEnableExtension(extension);
338 Release();
341 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated) {
342 const Extension* extension = NULL;
344 // Uninstall the extension if the abort was user initiated (and not, e.g., the
345 // result of the window closing).
346 // Otherwise, the extension will remain installed, but unacknowledged, so it
347 // will be prompted again.
348 if (user_initiated &&
349 service_weak_.get() &&
350 (extension = service_weak_->GetInstalledExtension(extension_id_))) {
351 service_weak_->UninstallExtension(extension_id_, false, NULL);
353 Release();
356 // ExternalInstallMenuAlert -------------------------------------------------
358 ExternalInstallMenuAlert::ExternalInstallMenuAlert(ExtensionService* service,
359 const Extension* extension)
360 : service_(service),
361 extension_(extension),
362 extension_registry_observer_(this) {
363 extension_registry_observer_.Add(ExtensionRegistry::Get(service->profile()));
364 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED,
365 content::Source<Profile>(service->profile()));
368 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
371 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
372 return SEVERITY_LOW;
375 bool ExternalInstallMenuAlert::HasMenuItem() {
376 return true;
379 int ExternalInstallMenuAlert::MenuItemCommandID() {
380 return kMenuCommandId;
383 base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
384 int id = -1;
385 if (extension_->is_app())
386 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
387 else if (extension_->is_theme())
388 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
389 else
390 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
391 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name()));
394 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
395 ShowExternalInstallDialog(service_, browser, extension_);
398 bool ExternalInstallMenuAlert::HasBubbleView() {
399 return false;
401 base::string16 ExternalInstallMenuAlert::GetBubbleViewTitle() {
402 return base::string16();
405 std::vector<base::string16> ExternalInstallMenuAlert::GetBubbleViewMessages() {
406 return std::vector<base::string16>();
409 base::string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
410 return base::string16();
413 base::string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
414 return base::string16();
417 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) {
418 NOTREACHED();
421 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
422 Browser* browser) {
423 NOTREACHED();
426 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
427 Browser* browser) {
428 NOTREACHED();
431 void ExternalInstallMenuAlert::OnExtensionLoaded(
432 content::BrowserContext* browser_context,
433 const Extension* extension) {
434 if (extension == extension_)
435 Clean();
438 void ExternalInstallMenuAlert::Observe(
439 int type,
440 const content::NotificationSource& source,
441 const content::NotificationDetails& details) {
442 // The error is invalidated if the extension has been loaded or removed.
443 DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_REMOVED);
444 const Extension* extension = content::Details<const Extension>(details).ptr();
445 if (extension == extension_)
446 Clean();
449 void ExternalInstallMenuAlert::Clean() {
450 GlobalErrorService* error_service =
451 GlobalErrorServiceFactory::GetForProfile(service_->profile());
452 error_service->RemoveGlobalError(this);
453 service_->AcknowledgeExternalExtension(extension_->id());
454 delete this;
457 // ExternalInstallGlobalError -----------------------------------------------
459 ExternalInstallGlobalError::ExternalInstallGlobalError(
460 ExtensionService* service,
461 const Extension* extension,
462 ExternalInstallDialogDelegate* delegate,
463 const ExtensionInstallPrompt::Prompt& prompt)
464 : ExternalInstallMenuAlert(service, extension),
465 delegate_(delegate),
466 prompt_(&prompt) {
469 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
470 if (delegate_)
471 delegate_->Release();
474 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) {
475 ShowBubbleView(browser);
478 bool ExternalInstallGlobalError::HasBubbleView() {
479 return true;
482 gfx::Image ExternalInstallGlobalError::GetBubbleViewIcon() {
483 if (prompt_->icon().IsEmpty())
484 return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
485 // Scale icon to a reasonable size.
486 return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
487 *prompt_->icon().ToImageSkia(),
488 skia::ImageOperations::RESIZE_BEST,
489 gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
490 extension_misc::EXTENSION_ICON_SMALL)));
493 base::string16 ExternalInstallGlobalError::GetBubbleViewTitle() {
494 return prompt_->GetDialogTitle();
497 std::vector<base::string16>
498 ExternalInstallGlobalError::GetBubbleViewMessages() {
499 std::vector<base::string16> messages;
500 messages.push_back(prompt_->GetHeading());
501 if (prompt_->GetPermissionCount()) {
502 messages.push_back(prompt_->GetPermissionsHeading());
503 for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) {
504 messages.push_back(l10n_util::GetStringFUTF16(
505 IDS_EXTENSION_PERMISSION_LINE,
506 prompt_->GetPermission(i)));
509 // TODO(yoz): OAuth issue advice?
510 return messages;
513 base::string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
514 return prompt_->GetAcceptButtonLabel();
517 base::string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
518 return prompt_->GetAbortButtonLabel();
521 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) {
524 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
525 Browser* browser) {
526 ExternalInstallDialogDelegate* delegate = delegate_;
527 delegate_ = NULL;
528 delegate->InstallUIProceed();
531 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
532 Browser* browser) {
533 ExternalInstallDialogDelegate* delegate = delegate_;
534 delegate_ = NULL;
535 delegate->InstallUIAbort(true);
538 // Public interface ---------------------------------------------------------
540 void AddExternalInstallError(ExtensionService* service,
541 const Extension* extension,
542 bool is_new_profile) {
543 GlobalErrorService* error_service =
544 GlobalErrorServiceFactory::GetForProfile(service->profile());
545 if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
546 return;
548 if (UseBubbleInstall(extension, is_new_profile)) {
549 Browser* browser = NULL;
550 #if !defined(OS_ANDROID)
551 browser = chrome::FindTabbedBrowser(service->profile(),
552 true,
553 chrome::GetActiveDesktop());
554 #endif
555 new ExternalInstallDialogDelegate(browser, service, extension, true);
556 } else {
557 error_service->AddGlobalError(
558 new ExternalInstallMenuAlert(service, extension));
562 void RemoveExternalInstallError(ExtensionService* service) {
563 GlobalErrorService* error_service =
564 GlobalErrorServiceFactory::GetForProfile(service->profile());
565 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
566 kMenuCommandId);
567 if (error) {
568 error_service->RemoveGlobalError(error);
569 delete error;
573 bool HasExternalInstallError(ExtensionService* service) {
574 GlobalErrorService* error_service =
575 GlobalErrorServiceFactory::GetForProfile(service->profile());
576 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
577 kMenuCommandId);
578 return !!error;
581 bool HasExternalInstallBubble(ExtensionService* service) {
582 GlobalErrorService* error_service =
583 GlobalErrorServiceFactory::GetForProfile(service->profile());
584 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
585 kMenuCommandId);
586 return error && error->HasBubbleView();
589 } // namespace extensions