blob: 7f23cfefb3892a2dcaabaf69f07510e6eacd271f [file] [log] [blame]
[email protected]ff7e68e2013-09-06 05:59:071// 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"
avi655876a2015-12-25 07:18:1510#include "base/macros.h"
hcarmona495d5f42015-11-24 22:20:5711#include "base/memory/weak_ptr.h"
12#include "base/scoped_observer.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1413#include "base/strings/utf_string_conversions.h"
skyostil380bb2222015-06-12 12:07:0514#include "base/thread_task_runner_handle.h"
[email protected]ff7e68e2013-09-06 05:59:0715#include "base/time/time.h"
16#include "chrome/browser/chrome_notification_types.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1417#include "chrome/browser/extensions/api/commands/command_service.h"
[email protected]a97883a882014-05-13 11:49:2518#include "chrome/browser/profiles/profile.h"
[email protected]ff7e68e2013-09-06 05:59:0719#include "chrome/browser/ui/browser.h"
rdevlin.cronin37d4de72015-12-12 03:39:3520#include "chrome/browser/ui/sync/sync_promo_ui.h"
[email protected]ff7e68e2013-09-06 05:59:0721#include "chrome/common/extensions/api/extension_action/action_info.h"
22#include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1423#include "chrome/common/extensions/command.h"
rdevlin.cronin37d4de72015-12-12 03:39:3524#include "chrome/common/extensions/sync_helper.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1425#include "chrome/grit/generated_resources.h"
hcarmona495d5f42015-11-24 22:20:5726#include "content/public/browser/notification_observer.h"
27#include "content/public/browser/notification_registrar.h"
[email protected]ff7e68e2013-09-06 05:59:0728#include "content/public/browser/notification_source.h"
[email protected]a97883a882014-05-13 11:49:2529#include "extensions/browser/extension_registry.h"
hcarmona495d5f42015-11-24 22:20:5730#include "extensions/browser/extension_registry_observer.h"
rdevlin.cronin37d4de72015-12-12 03:39:3531#include "extensions/common/feature_switch.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1432#include "ui/base/l10n/l10n_util.h"
[email protected]ff7e68e2013-09-06 05:59:0733
[email protected]ff7e68e2013-09-06 05:59:0734using extensions::Extension;
35
36namespace {
37
38// How long to wait for browser action animations to complete before retrying.
39const int kAnimationWaitMs = 50;
40// How often we retry when waiting for browser action animation to end.
41const int kAnimationWaitRetries = 10;
42
hcarmona495d5f42015-11-24 22:20:5743// Class responsible for showing the bubble after it's installed. Owns itself.
44class ExtensionInstalledBubbleObserver
45 : public content::NotificationObserver,
46 public extensions::ExtensionRegistryObserver {
47 public:
48 explicit ExtensionInstalledBubbleObserver(
49 scoped_ptr<ExtensionInstalledBubble> bubble)
50 : bubble_(bubble.Pass()),
51 extension_registry_observer_(this),
52 animation_wait_retries_(0),
53 weak_factory_(this) {
54 // |extension| has been initialized but not loaded at this point. We need to
55 // wait on showing the Bubble until the EXTENSION_LOADED gets fired.
56 extension_registry_observer_.Add(
57 extensions::ExtensionRegistry::Get(bubble_->browser()->profile()));
58
59 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING,
60 content::Source<Browser>(bubble_->browser()));
61 }
62
rdevlin.cronin305da4552015-12-09 01:52:0463 void Run() { OnExtensionLoaded(nullptr, bubble_->extension()); }
64
hcarmona495d5f42015-11-24 22:20:5765 private:
66 ~ExtensionInstalledBubbleObserver() override {}
67
68 // content::NotificationObserver:
69 void Observe(int type,
70 const content::NotificationSource& source,
71 const content::NotificationDetails& details) override {
72 DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_CLOSING)
73 << "Received unexpected notification";
74 // Browser is closing before the bubble was shown.
75 // TODO(hcarmona): Look into logging this with the BubbleManager.
76 delete this;
77 }
78
79 // extensions::ExtensionRegistryObserver:
80 void OnExtensionLoaded(content::BrowserContext* browser_context,
81 const extensions::Extension* extension) override {
82 if (extension == bubble_->extension()) {
hcarmona495d5f42015-11-24 22:20:5783 // PostTask to ourself to allow all EXTENSION_LOADED Observers to run.
84 // Only then can we be sure that a BrowserAction or PageAction has had
85 // views created which we can inspect for the purpose of previewing of
86 // pointing to them.
87 base::ThreadTaskRunnerHandle::Get()->PostTask(
rdevlin.croninf6b595d02015-12-17 20:08:2388 FROM_HERE, base::Bind(&ExtensionInstalledBubbleObserver::Initialize,
hcarmona495d5f42015-11-24 22:20:5789 weak_factory_.GetWeakPtr()));
90 }
91 }
92
93 void OnExtensionUnloaded(
94 content::BrowserContext* browser_context,
95 const extensions::Extension* extension,
96 extensions::UnloadedExtensionInfo::Reason reason) override {
97 if (extension == bubble_->extension()) {
98 // Extension is going away.
99 delete this;
100 }
101 }
102
rdevlin.croninf6b595d02015-12-17 20:08:23103 void Initialize() {
104 DCHECK(bubble_);
105 bubble_->Initialize();
106 Show();
107 }
108
hcarmona495d5f42015-11-24 22:20:57109 // Called internally via PostTask to show the bubble.
110 void Show() {
111 DCHECK(bubble_);
112 // TODO(hcarmona): Investigate having the BubbleManager query the bubble
113 // for |ShouldShow|. This is important because the BubbleManager may decide
114 // to delay showing the bubble.
115 if (bubble_->ShouldShow()) {
116 // Must be 2 lines because the manager will take ownership of bubble.
117 BubbleManager* manager = bubble_->browser()->GetBubbleManager();
118 manager->ShowBubble(bubble_.Pass());
119 delete this;
120 return;
121 }
122 if (animation_wait_retries_++ < kAnimationWaitRetries) {
123 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
124 FROM_HERE, base::Bind(&ExtensionInstalledBubbleObserver::Show,
125 weak_factory_.GetWeakPtr()),
126 base::TimeDelta::FromMilliseconds(kAnimationWaitMs));
127 } else {
128 // Retries are over; won't try again.
129 // TODO(hcarmona): Look into logging this with the BubbleManager.
130 delete this;
131 }
132 }
133
134 // The bubble that will be shown when the extension has finished installing.
135 scoped_ptr<ExtensionInstalledBubble> bubble_;
136
137 ScopedObserver<extensions::ExtensionRegistry,
138 extensions::ExtensionRegistryObserver>
139 extension_registry_observer_;
140
141 content::NotificationRegistrar registrar_;
142
143 // The number of times to retry showing the bubble if the bubble_->browser()
144 // action toolbar is animating.
145 int animation_wait_retries_;
146
147 base::WeakPtrFactory<ExtensionInstalledBubbleObserver> weak_factory_;
148
149 DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleObserver);
150};
151
rdevlin.croninf8a6bf62015-02-23 19:43:14152// Returns the keybinding for an extension command, or a null if none exists.
153scoped_ptr<extensions::Command> GetCommand(
154 const std::string& extension_id,
155 Profile* profile,
156 ExtensionInstalledBubble::BubbleType type) {
157 scoped_ptr<extensions::Command> result;
158 extensions::Command command;
159 extensions::CommandService* command_service =
160 extensions::CommandService::Get(profile);
161 bool has_command = false;
162 if (type == ExtensionInstalledBubble::BROWSER_ACTION) {
163 has_command = command_service->GetBrowserActionCommand(
164 extension_id, extensions::CommandService::ACTIVE, &command, nullptr);
165 } else if (type == ExtensionInstalledBubble::PAGE_ACTION) {
166 has_command = command_service->GetPageActionCommand(
167 extension_id, extensions::CommandService::ACTIVE, &command, nullptr);
168 }
169 if (has_command)
170 result.reset(new extensions::Command(command));
171 return result.Pass();
172}
173
hcarmona20059d82015-10-02 21:31:29174} // namespace
[email protected]ff7e68e2013-09-06 05:59:07175
hcarmona495d5f42015-11-24 22:20:57176// static
177void ExtensionInstalledBubble::ShowBubble(
178 const extensions::Extension* extension,
179 Browser* browser,
180 const SkBitmap& icon) {
181 // The ExtensionInstalledBubbleObserver will delete itself when the
182 // ExtensionInstalledBubble is shown or when it can't be shown anymore.
rdevlin.cronin305da4552015-12-09 01:52:04183 auto x = new ExtensionInstalledBubbleObserver(
hcarmona495d5f42015-11-24 22:20:57184 make_scoped_ptr(new ExtensionInstalledBubble(extension, browser, icon)));
rdevlin.cronin305da4552015-12-09 01:52:04185 extensions::ExtensionRegistry* reg =
186 extensions::ExtensionRegistry::Get(browser->profile());
187 if (reg->enabled_extensions().GetByID(extension->id())) {
188 x->Run();
189 }
hcarmona495d5f42015-11-24 22:20:57190}
191
hcarmona20059d82015-10-02 21:31:29192ExtensionInstalledBubble::ExtensionInstalledBubble(const Extension* extension,
193 Browser* browser,
194 const SkBitmap& icon)
rdevlin.cronin37d4de72015-12-12 03:39:35195 : extension_(extension),
196 browser_(browser),
197 icon_(icon),
198 type_(GENERIC),
199 options_(NONE),
200 anchor_position_(ANCHOR_APP_MENU) {
[email protected]ff7e68e2013-09-06 05:59:07201}
202
203ExtensionInstalledBubble::~ExtensionInstalledBubble() {}
204
hcarmona495d5f42015-11-24 22:20:57205bool ExtensionInstalledBubble::ShouldClose(BubbleCloseReason reason) const {
206 // Installing an extension triggers a navigation event that should be ignored.
207 return reason != BUBBLE_CLOSE_NAVIGATED;
208}
209
210std::string ExtensionInstalledBubble::GetName() const {
211 return "ExtensionInstalled";
[email protected]ff7e68e2013-09-06 05:59:07212}
213
rdevlin.croninf8a6bf62015-02-23 19:43:14214base::string16 ExtensionInstalledBubble::GetHowToUseDescription() const {
215 int message_id = 0;
216 base::string16 extra;
217 if (action_command_)
218 extra = action_command_->accelerator().GetShortcutText();
219
220 switch (type_) {
221 case BROWSER_ACTION:
222 message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO :
223 IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT;
224 break;
225 case PAGE_ACTION:
226 message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO :
227 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT;
228 break;
229 case OMNIBOX_KEYWORD:
230 extra =
231 base::UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(extension_));
232 message_id = IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO;
233 break;
234 case GENERIC:
235 break;
236 }
237
238 if (message_id == 0)
239 return base::string16();
240 return extra.empty() ? l10n_util::GetStringUTF16(message_id) :
241 l10n_util::GetStringFUTF16(message_id, extra);
242}
243
rdevlin.cronin37d4de72015-12-12 03:39:35244void ExtensionInstalledBubble::Initialize() {
rdevlin.croninf6b595d02015-12-17 20:08:23245 bool extension_action_redesign_on =
246 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
247
248 if (extensions::ActionInfo::GetBrowserActionInfo(extension_)) {
249 type_ = BROWSER_ACTION;
250 } else if (extensions::ActionInfo::GetPageActionInfo(extension_) &&
251 (extensions::ActionInfo::IsVerboseInstallMessage(extension_) ||
252 extension_action_redesign_on)) {
253 type_ = PAGE_ACTION;
254 } else if (!extensions::OmniboxInfo::GetKeyword(extension_).empty()) {
255 type_ = OMNIBOX_KEYWORD;
256 } else {
257 type_ = GENERIC;
258 }
259
hcarmona495d5f42015-11-24 22:20:57260 action_command_ = GetCommand(extension_->id(), browser_->profile(), type_);
rdevlin.cronin37d4de72015-12-12 03:39:35261 if (extensions::sync_helper::IsSyncable(extension_) &&
262 SyncPromoUI::ShouldShowSyncPromo(browser_->profile()))
263 options_ |= SIGN_IN_PROMO;
264
265 // Determine the bubble options we want, based on the extension type.
266 switch (type_) {
267 case BROWSER_ACTION:
268 case PAGE_ACTION:
269 options_ |= HOW_TO_USE;
270 if (has_command_keybinding()) {
271 options_ |= SHOW_KEYBINDING;
272 } else {
273 // The How-To-Use text makes the bubble seem a little crowded when the
274 // extension has a keybinding, so the How-To-Manage text is not shown
275 // in those cases.
276 options_ |= HOW_TO_MANAGE;
277 }
278
rdevlin.croninf6b595d02015-12-17 20:08:23279 if (type_ == BROWSER_ACTION || extension_action_redesign_on) {
rdevlin.cronin37d4de72015-12-12 03:39:35280 // If the toolbar redesign is enabled, all bubbles for extensions point
281 // to their toolbar action.
282 anchor_position_ = ANCHOR_BROWSER_ACTION;
283 } else {
284 DCHECK_EQ(type_, PAGE_ACTION);
285 anchor_position_ = ANCHOR_PAGE_ACTION;
286 }
287 break;
288 case OMNIBOX_KEYWORD:
289 options_ |= HOW_TO_USE | HOW_TO_MANAGE;
290 anchor_position_ = ANCHOR_OMNIBOX;
291 break;
292 case GENERIC:
293 anchor_position_ = ANCHOR_APP_MENU;
294 break;
295 }
[email protected]ff7e68e2013-09-06 05:59:07296}