blob: 74d65aa7642a6abe7fa71f2fd62953c7d24a84cf [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>
dcheng4a9d9822015-12-26 22:35:308#include <utility>
[email protected]ff7e68e2013-09-06 05:59:079
10#include "base/bind.h"
avi655876a2015-12-25 07:18:1511#include "base/macros.h"
hcarmona495d5f42015-11-24 22:20:5712#include "base/memory/weak_ptr.h"
13#include "base/scoped_observer.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1414#include "base/strings/utf_string_conversions.h"
skyostil380bb2222015-06-12 12:07:0515#include "base/thread_task_runner_handle.h"
[email protected]ff7e68e2013-09-06 05:59:0716#include "base/time/time.h"
17#include "chrome/browser/chrome_notification_types.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1418#include "chrome/browser/extensions/api/commands/command_service.h"
[email protected]a97883a882014-05-13 11:49:2519#include "chrome/browser/profiles/profile.h"
[email protected]ff7e68e2013-09-06 05:59:0720#include "chrome/browser/ui/browser.h"
rdevlin.cronin37d4de72015-12-12 03:39:3521#include "chrome/browser/ui/sync/sync_promo_ui.h"
[email protected]ff7e68e2013-09-06 05:59:0722#include "chrome/common/extensions/api/extension_action/action_info.h"
23#include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1424#include "chrome/common/extensions/command.h"
rdevlin.cronin37d4de72015-12-12 03:39:3525#include "chrome/common/extensions/sync_helper.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1426#include "chrome/grit/generated_resources.h"
hcarmona495d5f42015-11-24 22:20:5727#include "content/public/browser/notification_observer.h"
28#include "content/public/browser/notification_registrar.h"
[email protected]ff7e68e2013-09-06 05:59:0729#include "content/public/browser/notification_source.h"
[email protected]a97883a882014-05-13 11:49:2530#include "extensions/browser/extension_registry.h"
hcarmona495d5f42015-11-24 22:20:5731#include "extensions/browser/extension_registry_observer.h"
rdevlin.cronin37d4de72015-12-12 03:39:3532#include "extensions/common/feature_switch.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1433#include "ui/base/l10n/l10n_util.h"
[email protected]ff7e68e2013-09-06 05:59:0734
[email protected]ff7e68e2013-09-06 05:59:0735using extensions::Extension;
36
37namespace {
38
39// How long to wait for browser action animations to complete before retrying.
40const int kAnimationWaitMs = 50;
41// How often we retry when waiting for browser action animation to end.
42const int kAnimationWaitRetries = 10;
43
hcarmona495d5f42015-11-24 22:20:5744// Class responsible for showing the bubble after it's installed. Owns itself.
45class ExtensionInstalledBubbleObserver
46 : public content::NotificationObserver,
47 public extensions::ExtensionRegistryObserver {
48 public:
49 explicit ExtensionInstalledBubbleObserver(
50 scoped_ptr<ExtensionInstalledBubble> bubble)
dcheng4a9d9822015-12-26 22:35:3051 : bubble_(std::move(bubble)),
hcarmona495d5f42015-11-24 22:20:5752 extension_registry_observer_(this),
53 animation_wait_retries_(0),
54 weak_factory_(this) {
55 // |extension| has been initialized but not loaded at this point. We need to
56 // wait on showing the Bubble until the EXTENSION_LOADED gets fired.
57 extension_registry_observer_.Add(
58 extensions::ExtensionRegistry::Get(bubble_->browser()->profile()));
59
60 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING,
61 content::Source<Browser>(bubble_->browser()));
62 }
63
rdevlin.cronin305da4552015-12-09 01:52:0464 void Run() { OnExtensionLoaded(nullptr, bubble_->extension()); }
65
hcarmona495d5f42015-11-24 22:20:5766 private:
67 ~ExtensionInstalledBubbleObserver() override {}
68
69 // content::NotificationObserver:
70 void Observe(int type,
71 const content::NotificationSource& source,
72 const content::NotificationDetails& details) override {
73 DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_CLOSING)
74 << "Received unexpected notification";
75 // Browser is closing before the bubble was shown.
76 // TODO(hcarmona): Look into logging this with the BubbleManager.
77 delete this;
78 }
79
80 // extensions::ExtensionRegistryObserver:
81 void OnExtensionLoaded(content::BrowserContext* browser_context,
82 const extensions::Extension* extension) override {
83 if (extension == bubble_->extension()) {
hcarmona495d5f42015-11-24 22:20:5784 // PostTask to ourself to allow all EXTENSION_LOADED Observers to run.
85 // Only then can we be sure that a BrowserAction or PageAction has had
86 // views created which we can inspect for the purpose of previewing of
87 // pointing to them.
88 base::ThreadTaskRunnerHandle::Get()->PostTask(
rdevlin.croninf6b595d02015-12-17 20:08:2389 FROM_HERE, base::Bind(&ExtensionInstalledBubbleObserver::Initialize,
hcarmona495d5f42015-11-24 22:20:5790 weak_factory_.GetWeakPtr()));
91 }
92 }
93
94 void OnExtensionUnloaded(
95 content::BrowserContext* browser_context,
96 const extensions::Extension* extension,
97 extensions::UnloadedExtensionInfo::Reason reason) override {
98 if (extension == bubble_->extension()) {
99 // Extension is going away.
100 delete this;
101 }
102 }
103
rdevlin.croninf6b595d02015-12-17 20:08:23104 void Initialize() {
105 DCHECK(bubble_);
106 bubble_->Initialize();
107 Show();
108 }
109
hcarmona495d5f42015-11-24 22:20:57110 // Called internally via PostTask to show the bubble.
111 void Show() {
112 DCHECK(bubble_);
113 // TODO(hcarmona): Investigate having the BubbleManager query the bubble
114 // for |ShouldShow|. This is important because the BubbleManager may decide
115 // to delay showing the bubble.
116 if (bubble_->ShouldShow()) {
117 // Must be 2 lines because the manager will take ownership of bubble.
118 BubbleManager* manager = bubble_->browser()->GetBubbleManager();
dcheng4a9d9822015-12-26 22:35:30119 manager->ShowBubble(std::move(bubble_));
hcarmona495d5f42015-11-24 22:20:57120 delete this;
121 return;
122 }
123 if (animation_wait_retries_++ < kAnimationWaitRetries) {
124 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
125 FROM_HERE, base::Bind(&ExtensionInstalledBubbleObserver::Show,
126 weak_factory_.GetWeakPtr()),
127 base::TimeDelta::FromMilliseconds(kAnimationWaitMs));
128 } else {
129 // Retries are over; won't try again.
130 // TODO(hcarmona): Look into logging this with the BubbleManager.
131 delete this;
132 }
133 }
134
135 // The bubble that will be shown when the extension has finished installing.
136 scoped_ptr<ExtensionInstalledBubble> bubble_;
137
138 ScopedObserver<extensions::ExtensionRegistry,
139 extensions::ExtensionRegistryObserver>
140 extension_registry_observer_;
141
142 content::NotificationRegistrar registrar_;
143
144 // The number of times to retry showing the bubble if the bubble_->browser()
145 // action toolbar is animating.
146 int animation_wait_retries_;
147
148 base::WeakPtrFactory<ExtensionInstalledBubbleObserver> weak_factory_;
149
150 DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleObserver);
151};
152
rdevlin.croninf8a6bf62015-02-23 19:43:14153// Returns the keybinding for an extension command, or a null if none exists.
154scoped_ptr<extensions::Command> GetCommand(
155 const std::string& extension_id,
156 Profile* profile,
157 ExtensionInstalledBubble::BubbleType type) {
158 scoped_ptr<extensions::Command> result;
159 extensions::Command command;
160 extensions::CommandService* command_service =
161 extensions::CommandService::Get(profile);
162 bool has_command = false;
163 if (type == ExtensionInstalledBubble::BROWSER_ACTION) {
164 has_command = command_service->GetBrowserActionCommand(
165 extension_id, extensions::CommandService::ACTIVE, &command, nullptr);
166 } else if (type == ExtensionInstalledBubble::PAGE_ACTION) {
167 has_command = command_service->GetPageActionCommand(
168 extension_id, extensions::CommandService::ACTIVE, &command, nullptr);
169 }
170 if (has_command)
171 result.reset(new extensions::Command(command));
dcheng4a9d9822015-12-26 22:35:30172 return result;
rdevlin.croninf8a6bf62015-02-23 19:43:14173}
174
hcarmona20059d82015-10-02 21:31:29175} // namespace
[email protected]ff7e68e2013-09-06 05:59:07176
hcarmona495d5f42015-11-24 22:20:57177// static
178void ExtensionInstalledBubble::ShowBubble(
179 const extensions::Extension* extension,
180 Browser* browser,
181 const SkBitmap& icon) {
182 // The ExtensionInstalledBubbleObserver will delete itself when the
183 // ExtensionInstalledBubble is shown or when it can't be shown anymore.
rdevlin.cronin305da4552015-12-09 01:52:04184 auto x = new ExtensionInstalledBubbleObserver(
hcarmona495d5f42015-11-24 22:20:57185 make_scoped_ptr(new ExtensionInstalledBubble(extension, browser, icon)));
rdevlin.cronin305da4552015-12-09 01:52:04186 extensions::ExtensionRegistry* reg =
187 extensions::ExtensionRegistry::Get(browser->profile());
188 if (reg->enabled_extensions().GetByID(extension->id())) {
189 x->Run();
190 }
hcarmona495d5f42015-11-24 22:20:57191}
192
hcarmona20059d82015-10-02 21:31:29193ExtensionInstalledBubble::ExtensionInstalledBubble(const Extension* extension,
194 Browser* browser,
195 const SkBitmap& icon)
rdevlin.cronin37d4de72015-12-12 03:39:35196 : extension_(extension),
197 browser_(browser),
198 icon_(icon),
199 type_(GENERIC),
200 options_(NONE),
201 anchor_position_(ANCHOR_APP_MENU) {
[email protected]ff7e68e2013-09-06 05:59:07202}
203
204ExtensionInstalledBubble::~ExtensionInstalledBubble() {}
205
hcarmona495d5f42015-11-24 22:20:57206bool ExtensionInstalledBubble::ShouldClose(BubbleCloseReason reason) const {
207 // Installing an extension triggers a navigation event that should be ignored.
208 return reason != BUBBLE_CLOSE_NAVIGATED;
209}
210
211std::string ExtensionInstalledBubble::GetName() const {
212 return "ExtensionInstalled";
[email protected]ff7e68e2013-09-06 05:59:07213}
214
rdevlin.croninf8a6bf62015-02-23 19:43:14215base::string16 ExtensionInstalledBubble::GetHowToUseDescription() const {
216 int message_id = 0;
217 base::string16 extra;
218 if (action_command_)
219 extra = action_command_->accelerator().GetShortcutText();
220
221 switch (type_) {
222 case BROWSER_ACTION:
223 message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO :
224 IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT;
225 break;
226 case PAGE_ACTION:
227 message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO :
228 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT;
229 break;
230 case OMNIBOX_KEYWORD:
231 extra =
232 base::UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(extension_));
233 message_id = IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO;
234 break;
235 case GENERIC:
236 break;
237 }
238
239 if (message_id == 0)
240 return base::string16();
241 return extra.empty() ? l10n_util::GetStringUTF16(message_id) :
242 l10n_util::GetStringFUTF16(message_id, extra);
243}
244
rdevlin.cronin37d4de72015-12-12 03:39:35245void ExtensionInstalledBubble::Initialize() {
rdevlin.croninf6b595d02015-12-17 20:08:23246 bool extension_action_redesign_on =
247 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled();
248
249 if (extensions::ActionInfo::GetBrowserActionInfo(extension_)) {
250 type_ = BROWSER_ACTION;
251 } else if (extensions::ActionInfo::GetPageActionInfo(extension_) &&
252 (extensions::ActionInfo::IsVerboseInstallMessage(extension_) ||
253 extension_action_redesign_on)) {
254 type_ = PAGE_ACTION;
255 } else if (!extensions::OmniboxInfo::GetKeyword(extension_).empty()) {
256 type_ = OMNIBOX_KEYWORD;
257 } else {
258 type_ = GENERIC;
259 }
260
hcarmona495d5f42015-11-24 22:20:57261 action_command_ = GetCommand(extension_->id(), browser_->profile(), type_);
rdevlin.cronin37d4de72015-12-12 03:39:35262 if (extensions::sync_helper::IsSyncable(extension_) &&
263 SyncPromoUI::ShouldShowSyncPromo(browser_->profile()))
264 options_ |= SIGN_IN_PROMO;
265
266 // Determine the bubble options we want, based on the extension type.
267 switch (type_) {
268 case BROWSER_ACTION:
269 case PAGE_ACTION:
270 options_ |= HOW_TO_USE;
271 if (has_command_keybinding()) {
272 options_ |= SHOW_KEYBINDING;
273 } else {
274 // The How-To-Use text makes the bubble seem a little crowded when the
275 // extension has a keybinding, so the How-To-Manage text is not shown
276 // in those cases.
277 options_ |= HOW_TO_MANAGE;
278 }
279
rdevlin.croninf6b595d02015-12-17 20:08:23280 if (type_ == BROWSER_ACTION || extension_action_redesign_on) {
rdevlin.cronin37d4de72015-12-12 03:39:35281 // If the toolbar redesign is enabled, all bubbles for extensions point
282 // to their toolbar action.
283 anchor_position_ = ANCHOR_BROWSER_ACTION;
284 } else {
285 DCHECK_EQ(type_, PAGE_ACTION);
286 anchor_position_ = ANCHOR_PAGE_ACTION;
287 }
288 break;
289 case OMNIBOX_KEYWORD:
290 options_ |= HOW_TO_USE | HOW_TO_MANAGE;
291 anchor_position_ = ANCHOR_OMNIBOX;
292 break;
293 case GENERIC:
294 anchor_position_ = ANCHOR_APP_MENU;
295 break;
296 }
[email protected]ff7e68e2013-09-06 05:59:07297}