blob: 7f0365dc9f1ea2e4032381bc657fef5be92ae079 [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"
hcarmona495d5f42015-11-24 22:20:5710#include "base/memory/weak_ptr.h"
11#include "base/scoped_observer.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1412#include "base/strings/utf_string_conversions.h"
skyostil380bb2222015-06-12 12:07:0513#include "base/thread_task_runner_handle.h"
[email protected]ff7e68e2013-09-06 05:59:0714#include "base/time/time.h"
15#include "chrome/browser/chrome_notification_types.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1416#include "chrome/browser/extensions/api/commands/command_service.h"
[email protected]a97883a882014-05-13 11:49:2517#include "chrome/browser/profiles/profile.h"
[email protected]ff7e68e2013-09-06 05:59:0718#include "chrome/browser/ui/browser.h"
rdevlin.cronin37d4de72015-12-12 03:39:3519#include "chrome/browser/ui/sync/sync_promo_ui.h"
[email protected]ff7e68e2013-09-06 05:59:0720#include "chrome/common/extensions/api/extension_action/action_info.h"
21#include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1422#include "chrome/common/extensions/command.h"
rdevlin.cronin37d4de72015-12-12 03:39:3523#include "chrome/common/extensions/sync_helper.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1424#include "chrome/grit/generated_resources.h"
hcarmona495d5f42015-11-24 22:20:5725#include "content/public/browser/notification_observer.h"
26#include "content/public/browser/notification_registrar.h"
[email protected]ff7e68e2013-09-06 05:59:0727#include "content/public/browser/notification_source.h"
[email protected]a97883a882014-05-13 11:49:2528#include "extensions/browser/extension_registry.h"
hcarmona495d5f42015-11-24 22:20:5729#include "extensions/browser/extension_registry_observer.h"
rdevlin.cronin37d4de72015-12-12 03:39:3530#include "extensions/common/feature_switch.h"
rdevlin.croninf8a6bf62015-02-23 19:43:1431#include "ui/base/l10n/l10n_util.h"
[email protected]ff7e68e2013-09-06 05:59:0732
[email protected]ff7e68e2013-09-06 05:59:0733using extensions::Extension;
34
35namespace {
36
37// How long to wait for browser action animations to complete before retrying.
38const int kAnimationWaitMs = 50;
39// How often we retry when waiting for browser action animation to end.
40const int kAnimationWaitRetries = 10;
41
hcarmona495d5f42015-11-24 22:20:5742// Class responsible for showing the bubble after it's installed. Owns itself.
43class ExtensionInstalledBubbleObserver
44 : public content::NotificationObserver,
45 public extensions::ExtensionRegistryObserver {
46 public:
47 explicit ExtensionInstalledBubbleObserver(
48 scoped_ptr<ExtensionInstalledBubble> bubble)
49 : bubble_(bubble.Pass()),
50 extension_registry_observer_(this),
51 animation_wait_retries_(0),
52 weak_factory_(this) {
53 // |extension| has been initialized but not loaded at this point. We need to
54 // wait on showing the Bubble until the EXTENSION_LOADED gets fired.
55 extension_registry_observer_.Add(
56 extensions::ExtensionRegistry::Get(bubble_->browser()->profile()));
57
58 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING,
59 content::Source<Browser>(bubble_->browser()));
60 }
61
rdevlin.cronin305da4552015-12-09 01:52:0462 void Run() { OnExtensionLoaded(nullptr, bubble_->extension()); }
63
hcarmona495d5f42015-11-24 22:20:5764 private:
65 ~ExtensionInstalledBubbleObserver() override {}
66
67 // content::NotificationObserver:
68 void Observe(int type,
69 const content::NotificationSource& source,
70 const content::NotificationDetails& details) override {
71 DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_CLOSING)
72 << "Received unexpected notification";
73 // Browser is closing before the bubble was shown.
74 // TODO(hcarmona): Look into logging this with the BubbleManager.
75 delete this;
76 }
77
78 // extensions::ExtensionRegistryObserver:
79 void OnExtensionLoaded(content::BrowserContext* browser_context,
80 const extensions::Extension* extension) override {
81 if (extension == bubble_->extension()) {
rdevlin.cronin37d4de72015-12-12 03:39:3582 bubble_->Initialize();
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(
88 FROM_HERE, base::Bind(&ExtensionInstalledBubbleObserver::Show,
89 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
103 // Called internally via PostTask to show the bubble.
104 void Show() {
105 DCHECK(bubble_);
106 // TODO(hcarmona): Investigate having the BubbleManager query the bubble
107 // for |ShouldShow|. This is important because the BubbleManager may decide
108 // to delay showing the bubble.
109 if (bubble_->ShouldShow()) {
110 // Must be 2 lines because the manager will take ownership of bubble.
111 BubbleManager* manager = bubble_->browser()->GetBubbleManager();
112 manager->ShowBubble(bubble_.Pass());
113 delete this;
114 return;
115 }
116 if (animation_wait_retries_++ < kAnimationWaitRetries) {
117 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
118 FROM_HERE, base::Bind(&ExtensionInstalledBubbleObserver::Show,
119 weak_factory_.GetWeakPtr()),
120 base::TimeDelta::FromMilliseconds(kAnimationWaitMs));
121 } else {
122 // Retries are over; won't try again.
123 // TODO(hcarmona): Look into logging this with the BubbleManager.
124 delete this;
125 }
126 }
127
128 // The bubble that will be shown when the extension has finished installing.
129 scoped_ptr<ExtensionInstalledBubble> bubble_;
130
131 ScopedObserver<extensions::ExtensionRegistry,
132 extensions::ExtensionRegistryObserver>
133 extension_registry_observer_;
134
135 content::NotificationRegistrar registrar_;
136
137 // The number of times to retry showing the bubble if the bubble_->browser()
138 // action toolbar is animating.
139 int animation_wait_retries_;
140
141 base::WeakPtrFactory<ExtensionInstalledBubbleObserver> weak_factory_;
142
143 DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleObserver);
144};
145
rdevlin.croninf8a6bf62015-02-23 19:43:14146// Returns the keybinding for an extension command, or a null if none exists.
147scoped_ptr<extensions::Command> GetCommand(
148 const std::string& extension_id,
149 Profile* profile,
150 ExtensionInstalledBubble::BubbleType type) {
151 scoped_ptr<extensions::Command> result;
152 extensions::Command command;
153 extensions::CommandService* command_service =
154 extensions::CommandService::Get(profile);
155 bool has_command = false;
156 if (type == ExtensionInstalledBubble::BROWSER_ACTION) {
157 has_command = command_service->GetBrowserActionCommand(
158 extension_id, extensions::CommandService::ACTIVE, &command, nullptr);
159 } else if (type == ExtensionInstalledBubble::PAGE_ACTION) {
160 has_command = command_service->GetPageActionCommand(
161 extension_id, extensions::CommandService::ACTIVE, &command, nullptr);
162 }
163 if (has_command)
164 result.reset(new extensions::Command(command));
165 return result.Pass();
166}
167
hcarmona20059d82015-10-02 21:31:29168} // namespace
[email protected]ff7e68e2013-09-06 05:59:07169
hcarmona495d5f42015-11-24 22:20:57170// static
171void ExtensionInstalledBubble::ShowBubble(
172 const extensions::Extension* extension,
173 Browser* browser,
174 const SkBitmap& icon) {
175 // The ExtensionInstalledBubbleObserver will delete itself when the
176 // ExtensionInstalledBubble is shown or when it can't be shown anymore.
rdevlin.cronin305da4552015-12-09 01:52:04177 auto x = new ExtensionInstalledBubbleObserver(
hcarmona495d5f42015-11-24 22:20:57178 make_scoped_ptr(new ExtensionInstalledBubble(extension, browser, icon)));
rdevlin.cronin305da4552015-12-09 01:52:04179 extensions::ExtensionRegistry* reg =
180 extensions::ExtensionRegistry::Get(browser->profile());
181 if (reg->enabled_extensions().GetByID(extension->id())) {
182 x->Run();
183 }
hcarmona495d5f42015-11-24 22:20:57184}
185
hcarmona20059d82015-10-02 21:31:29186ExtensionInstalledBubble::ExtensionInstalledBubble(const Extension* extension,
187 Browser* browser,
188 const SkBitmap& icon)
rdevlin.cronin37d4de72015-12-12 03:39:35189 : extension_(extension),
190 browser_(browser),
191 icon_(icon),
192 type_(GENERIC),
193 options_(NONE),
194 anchor_position_(ANCHOR_APP_MENU) {
[email protected]ff7e68e2013-09-06 05:59:07195 if (!extensions::OmniboxInfo::GetKeyword(extension).empty())
196 type_ = OMNIBOX_KEYWORD;
197 else if (extensions::ActionInfo::GetBrowserActionInfo(extension))
198 type_ = BROWSER_ACTION;
199 else if (extensions::ActionInfo::GetPageActionInfo(extension) &&
200 extensions::ActionInfo::IsVerboseInstallMessage(extension))
201 type_ = PAGE_ACTION;
202 else
203 type_ = GENERIC;
[email protected]ff7e68e2013-09-06 05:59:07204}
205
206ExtensionInstalledBubble::~ExtensionInstalledBubble() {}
207
hcarmona495d5f42015-11-24 22:20:57208bool ExtensionInstalledBubble::ShouldClose(BubbleCloseReason reason) const {
209 // Installing an extension triggers a navigation event that should be ignored.
210 return reason != BUBBLE_CLOSE_NAVIGATED;
211}
212
213std::string ExtensionInstalledBubble::GetName() const {
214 return "ExtensionInstalled";
[email protected]ff7e68e2013-09-06 05:59:07215}
216
rdevlin.croninf8a6bf62015-02-23 19:43:14217base::string16 ExtensionInstalledBubble::GetHowToUseDescription() const {
218 int message_id = 0;
219 base::string16 extra;
220 if (action_command_)
221 extra = action_command_->accelerator().GetShortcutText();
222
223 switch (type_) {
224 case BROWSER_ACTION:
225 message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO :
226 IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT;
227 break;
228 case PAGE_ACTION:
229 message_id = extra.empty() ? IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO :
230 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT;
231 break;
232 case OMNIBOX_KEYWORD:
233 extra =
234 base::UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(extension_));
235 message_id = IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO;
236 break;
237 case GENERIC:
238 break;
239 }
240
241 if (message_id == 0)
242 return base::string16();
243 return extra.empty() ? l10n_util::GetStringUTF16(message_id) :
244 l10n_util::GetStringFUTF16(message_id, extra);
245}
246
rdevlin.cronin37d4de72015-12-12 03:39:35247void ExtensionInstalledBubble::Initialize() {
hcarmona495d5f42015-11-24 22:20:57248 action_command_ = GetCommand(extension_->id(), browser_->profile(), type_);
rdevlin.cronin37d4de72015-12-12 03:39:35249 if (extensions::sync_helper::IsSyncable(extension_) &&
250 SyncPromoUI::ShouldShowSyncPromo(browser_->profile()))
251 options_ |= SIGN_IN_PROMO;
252
253 // Determine the bubble options we want, based on the extension type.
254 switch (type_) {
255 case BROWSER_ACTION:
256 case PAGE_ACTION:
257 options_ |= HOW_TO_USE;
258 if (has_command_keybinding()) {
259 options_ |= SHOW_KEYBINDING;
260 } else {
261 // The How-To-Use text makes the bubble seem a little crowded when the
262 // extension has a keybinding, so the How-To-Manage text is not shown
263 // in those cases.
264 options_ |= HOW_TO_MANAGE;
265 }
266
267 if (type_ == BROWSER_ACTION ||
268 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) {
269 // If the toolbar redesign is enabled, all bubbles for extensions point
270 // to their toolbar action.
271 anchor_position_ = ANCHOR_BROWSER_ACTION;
272 } else {
273 DCHECK_EQ(type_, PAGE_ACTION);
274 anchor_position_ = ANCHOR_PAGE_ACTION;
275 }
276 break;
277 case OMNIBOX_KEYWORD:
278 options_ |= HOW_TO_USE | HOW_TO_MANAGE;
279 anchor_position_ = ANCHOR_OMNIBOX;
280 break;
281 case GENERIC:
282 anchor_position_ = ANCHOR_APP_MENU;
283 break;
284 }
[email protected]ff7e68e2013-09-06 05:59:07285}