[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 1 | // Copyright 2013 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/ui/extensions/extension_installed_bubble.h" |
| 6 | |
| 7 | #include <string> |
| 8 | |
| 9 | #include "base/bind.h" |
skyostil | 380bb222 | 2015-06-12 12:07:05 | [diff] [blame] | 10 | #include "base/location.h" |
| 11 | #include "base/single_thread_task_runner.h" |
rdevlin.cronin | f8a6bf6 | 2015-02-23 19:43:14 | [diff] [blame] | 12 | #include "base/strings/utf_string_conversions.h" |
skyostil | 380bb222 | 2015-06-12 12:07:05 | [diff] [blame] | 13 | #include "base/thread_task_runner_handle.h" |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 14 | #include "base/time/time.h" |
| 15 | #include "chrome/browser/chrome_notification_types.h" |
rdevlin.cronin | f8a6bf6 | 2015-02-23 19:43:14 | [diff] [blame] | 16 | #include "chrome/browser/extensions/api/commands/command_service.h" |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 17 | #include "chrome/browser/profiles/profile.h" |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 18 | #include "chrome/browser/ui/browser.h" |
| 19 | #include "chrome/common/extensions/api/extension_action/action_info.h" |
| 20 | #include "chrome/common/extensions/api/omnibox/omnibox_handler.h" |
rdevlin.cronin | f8a6bf6 | 2015-02-23 19:43:14 | [diff] [blame] | 21 | #include "chrome/common/extensions/command.h" |
| 22 | #include "chrome/grit/generated_resources.h" |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 23 | #include "content/public/browser/notification_details.h" |
| 24 | #include "content/public/browser/notification_source.h" |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 25 | #include "extensions/browser/extension_registry.h" |
[email protected] | e4452d3 | 2013-11-15 23:07:41 | [diff] [blame] | 26 | #include "extensions/common/extension.h" |
rdevlin.cronin | f8a6bf6 | 2015-02-23 19:43:14 | [diff] [blame] | 27 | #include "ui/base/l10n/l10n_util.h" |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 28 | |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 29 | using extensions::Extension; |
| 30 | |
| 31 | namespace { |
| 32 | |
| 33 | // How long to wait for browser action animations to complete before retrying. |
| 34 | const int kAnimationWaitMs = 50; |
| 35 | // How often we retry when waiting for browser action animation to end. |
| 36 | const int kAnimationWaitRetries = 10; |
| 37 | |
rdevlin.cronin | f8a6bf6 | 2015-02-23 19:43:14 | [diff] [blame] | 38 | // Returns the keybinding for an extension command, or a null if none exists. |
| 39 | scoped_ptr<extensions::Command> GetCommand( |
| 40 | const std::string& extension_id, |
| 41 | Profile* profile, |
| 42 | ExtensionInstalledBubble::BubbleType type) { |
| 43 | scoped_ptr<extensions::Command> result; |
| 44 | extensions::Command command; |
| 45 | extensions::CommandService* command_service = |
| 46 | extensions::CommandService::Get(profile); |
| 47 | bool has_command = false; |
| 48 | if (type == ExtensionInstalledBubble::BROWSER_ACTION) { |
| 49 | has_command = command_service->GetBrowserActionCommand( |
| 50 | extension_id, extensions::CommandService::ACTIVE, &command, nullptr); |
| 51 | } else if (type == ExtensionInstalledBubble::PAGE_ACTION) { |
| 52 | has_command = command_service->GetPageActionCommand( |
| 53 | extension_id, extensions::CommandService::ACTIVE, &command, nullptr); |
| 54 | } |
| 55 | if (has_command) |
| 56 | result.reset(new extensions::Command(command)); |
| 57 | return result.Pass(); |
| 58 | } |
| 59 | |
hcarmona | 20059d8 | 2015-10-02 21:31:29 | [diff] [blame^] | 60 | } // namespace |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 61 | |
hcarmona | 20059d8 | 2015-10-02 21:31:29 | [diff] [blame^] | 62 | ExtensionInstalledBubble::ExtensionInstalledBubble(const Extension* extension, |
| 63 | Browser* browser, |
| 64 | const SkBitmap& icon) |
| 65 | : bubble_ui_(nullptr), |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 66 | extension_(extension), |
| 67 | browser_(browser), |
| 68 | icon_(icon), |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 69 | extension_registry_observer_(this), |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 70 | animation_wait_retries_(0), |
| 71 | weak_factory_(this) { |
| 72 | if (!extensions::OmniboxInfo::GetKeyword(extension).empty()) |
| 73 | type_ = OMNIBOX_KEYWORD; |
| 74 | else if (extensions::ActionInfo::GetBrowserActionInfo(extension)) |
| 75 | type_ = BROWSER_ACTION; |
| 76 | else if (extensions::ActionInfo::GetPageActionInfo(extension) && |
| 77 | extensions::ActionInfo::IsVerboseInstallMessage(extension)) |
| 78 | type_ = PAGE_ACTION; |
| 79 | else |
| 80 | type_ = GENERIC; |
| 81 | |
| 82 | // |extension| has been initialized but not loaded at this point. We need |
| 83 | // to wait on showing the Bubble until not only the EXTENSION_LOADED gets |
| 84 | // fired, but all of the EXTENSION_LOADED Observers have run. Only then can we |
| 85 | // be sure that a BrowserAction or PageAction has had views created which we |
| 86 | // can inspect for the purpose of previewing of pointing to them. |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 87 | extension_registry_observer_.Add( |
| 88 | extensions::ExtensionRegistry::Get(browser->profile())); |
| 89 | |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 90 | registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING, |
| 91 | content::Source<Browser>(browser)); |
| 92 | } |
| 93 | |
| 94 | ExtensionInstalledBubble::~ExtensionInstalledBubble() {} |
| 95 | |
| 96 | void ExtensionInstalledBubble::IgnoreBrowserClosing() { |
| 97 | registrar_.Remove(this, chrome::NOTIFICATION_BROWSER_CLOSING, |
| 98 | content::Source<Browser>(browser_)); |
| 99 | } |
| 100 | |
rdevlin.cronin | f8a6bf6 | 2015-02-23 19:43:14 | [diff] [blame] | 101 | base::string16 ExtensionInstalledBubble::GetHowToUseDescription() const { |
| 102 | int message_id = 0; |
| 103 | base::string16 extra; |
| 104 | if (action_command_) |
| 105 | extra = action_command_->accelerator().GetShortcutText(); |
| 106 | |
| 107 | switch (type_) { |
| 108 | case BROWSER_ACTION: |
| 109 | message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO : |
| 110 | IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT; |
| 111 | break; |
| 112 | case PAGE_ACTION: |
| 113 | message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO : |
| 114 | IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT; |
| 115 | break; |
| 116 | case OMNIBOX_KEYWORD: |
| 117 | extra = |
| 118 | base::UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(extension_)); |
| 119 | message_id = IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO; |
| 120 | break; |
| 121 | case GENERIC: |
| 122 | break; |
| 123 | } |
| 124 | |
| 125 | if (message_id == 0) |
| 126 | return base::string16(); |
| 127 | return extra.empty() ? l10n_util::GetStringUTF16(message_id) : |
| 128 | l10n_util::GetStringFUTF16(message_id, extra); |
| 129 | } |
| 130 | |
hcarmona | 20059d8 | 2015-10-02 21:31:29 | [diff] [blame^] | 131 | void ExtensionInstalledBubble::SetBubbleUi( |
| 132 | ExtensionInstalledBubbleUi* bubble_ui) { |
| 133 | DCHECK(!bubble_ui_); |
| 134 | DCHECK(bubble_ui); |
| 135 | bubble_ui_ = bubble_ui; |
| 136 | } |
| 137 | |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 138 | void ExtensionInstalledBubble::ShowInternal() { |
hcarmona | a695cf92 | 2015-10-02 18:06:15 | [diff] [blame] | 139 | if (ExtensionInstalledBubbleUi::ShouldShow(this)) { |
hcarmona | 20059d8 | 2015-10-02 21:31:29 | [diff] [blame^] | 140 | DCHECK(bubble_ui_); |
hcarmona | a695cf92 | 2015-10-02 18:06:15 | [diff] [blame] | 141 | bubble_ui_->Show(); |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 142 | return; |
hcarmona | a695cf92 | 2015-10-02 18:06:15 | [diff] [blame] | 143 | } |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 144 | if (animation_wait_retries_++ < kAnimationWaitRetries) { |
skyostil | 380bb222 | 2015-06-12 12:07:05 | [diff] [blame] | 145 | base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| 146 | FROM_HERE, base::Bind(&ExtensionInstalledBubble::ShowInternal, |
| 147 | weak_factory_.GetWeakPtr()), |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 148 | base::TimeDelta::FromMilliseconds(kAnimationWaitMs)); |
| 149 | } |
| 150 | } |
| 151 | |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 152 | void ExtensionInstalledBubble::OnExtensionLoaded( |
| 153 | content::BrowserContext* browser_context, |
| 154 | const extensions::Extension* extension) { |
| 155 | if (extension == extension_) { |
rdevlin.cronin | f8a6bf6 | 2015-02-23 19:43:14 | [diff] [blame] | 156 | // Parse the extension command, if one exists. |
| 157 | action_command_ = GetCommand(extension_->id(), browser_->profile(), type_); |
| 158 | |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 159 | animation_wait_retries_ = 0; |
| 160 | // PostTask to ourself to allow all EXTENSION_LOADED Observers to run. |
skyostil | 380bb222 | 2015-06-12 12:07:05 | [diff] [blame] | 161 | base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 162 | FROM_HERE, base::Bind(&ExtensionInstalledBubble::ShowInternal, |
| 163 | weak_factory_.GetWeakPtr())); |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 164 | } |
| 165 | } |
| 166 | |
| 167 | void ExtensionInstalledBubble::OnExtensionUnloaded( |
| 168 | content::BrowserContext* browser_context, |
| 169 | const extensions::Extension* extension, |
| 170 | extensions::UnloadedExtensionInfo::Reason reason) { |
| 171 | if (extension == extension_) { |
| 172 | // Extension is going away, make sure ShowInternal won't be called. |
| 173 | weak_factory_.InvalidateWeakPtrs(); |
| 174 | extension_ = NULL; |
| 175 | } |
| 176 | } |
| 177 | |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 178 | void ExtensionInstalledBubble::Observe( |
| 179 | int type, |
| 180 | const content::NotificationSource& source, |
| 181 | const content::NotificationDetails& details) { |
[email protected] | a97883a88 | 2014-05-13 11:49:25 | [diff] [blame] | 182 | DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_CLOSING) |
| 183 | << "Received unexpected notification"; |
hcarmona | db25c2a | 2015-10-02 00:16:37 | [diff] [blame] | 184 | delete bubble_ui_; |
[email protected] | ff7e68e | 2013-09-06 05:59:07 | [diff] [blame] | 185 | } |