OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/ui/views/extensions/extension_installed_bubble.h" | |
6 | |
7 #include <algorithm> | |
8 #include <string> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/i18n/rtl.h" | |
12 #include "base/message_loop/message_loop.h" | |
13 #include "base/strings/utf_string_conversions.h" | |
14 #include "chrome/browser/chrome_notification_types.h" | |
15 #include "chrome/browser/extensions/api/commands/command_service.h" | |
16 #include "chrome/browser/extensions/extension_action.h" | |
17 #include "chrome/browser/extensions/extension_action_manager.h" | |
18 #include "chrome/browser/extensions/extension_install_ui.h" | |
19 #include "chrome/browser/profiles/profile.h" | |
20 #include "chrome/browser/signin/signin_promo.h" | |
21 #include "chrome/browser/ui/browser.h" | |
22 #include "chrome/browser/ui/browser_window.h" | |
23 #include "chrome/browser/ui/singleton_tabs.h" | |
24 #include "chrome/browser/ui/sync/sync_promo_ui.h" | |
25 #include "chrome/browser/ui/views/browser_action_view.h" | |
26 #include "chrome/browser/ui/views/browser_actions_container.h" | |
27 #include "chrome/browser/ui/views/frame/browser_view.h" | |
28 #include "chrome/browser/ui/views/location_bar/location_bar_view.h" | |
29 #include "chrome/browser/ui/views/tabs/tab_strip.h" | |
30 #include "chrome/browser/ui/views/toolbar_view.h" | |
31 #include "chrome/common/extensions/api/extension_action/action_info.h" | |
32 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h" | |
33 #include "chrome/common/extensions/extension.h" | |
34 #include "chrome/common/extensions/sync_helper.h" | |
35 #include "chrome/common/url_constants.h" | |
36 #include "content/public/browser/notification_details.h" | |
37 #include "content/public/browser/notification_source.h" | |
38 #include "grit/chromium_strings.h" | |
39 #include "grit/generated_resources.h" | |
40 #include "grit/ui_resources.h" | |
41 #include "ui/base/l10n/l10n_util.h" | |
42 #include "ui/base/resource/resource_bundle.h" | |
43 #include "ui/base/text/text_elider.h" | |
44 #include "ui/gfx/render_text.h" | |
45 #include "ui/views/controls/button/image_button.h" | |
46 #include "ui/views/controls/image_view.h" | |
47 #include "ui/views/controls/label.h" | |
48 #include "ui/views/controls/link.h" | |
49 #include "ui/views/controls/link_listener.h" | |
50 #include "ui/views/layout/fill_layout.h" | |
51 #include "ui/views/layout/layout_constants.h" | |
52 | |
53 using extensions::Extension; | |
54 | |
55 namespace { | |
56 | |
57 const int kIconSize = 43; | |
58 | |
59 const int kRightColumnWidth = 285; | |
60 | |
61 // The Bubble uses a BubbleBorder which adds about 6 pixels of whitespace | |
62 // around the content view. We compensate by reducing our outer borders by this | |
63 // amount + 4px. | |
64 const int kOuterMarginInset = 10; | |
65 const int kHorizOuterMargin = views::kPanelHorizMargin - kOuterMarginInset; | |
66 const int kVertOuterMargin = views::kPanelVertMargin - kOuterMarginInset; | |
67 | |
68 // Interior vertical margin is 8px smaller than standard | |
69 const int kVertInnerMargin = views::kPanelVertMargin - 8; | |
70 | |
71 // We want to shift the right column (which contains the header and text) up | |
72 // 4px to align with icon. | |
73 const int kRightcolumnVerticalShift = -4; | |
74 | |
75 // How long to wait for browser action animations to complete before retrying. | |
76 const int kAnimationWaitTime = 50; | |
77 | |
78 // How often we retry when waiting for browser action animation to end. | |
79 const int kAnimationWaitMaxRetry = 10; | |
80 | |
81 } // namespace | |
82 | |
83 namespace chrome { | |
84 | |
85 void ShowExtensionInstalledBubble(const Extension* extension, | |
86 Browser* browser, | |
87 const SkBitmap& icon) { | |
88 ExtensionInstalledBubble::Show(extension, browser, icon); | |
89 } | |
90 | |
91 } // namespace chrome | |
92 | |
93 // InstalledBubbleContent is the content view which is placed in the | |
94 // ExtensionInstalledBubble. It displays the install icon and explanatory | |
95 // text about the installed extension. | |
96 class InstalledBubbleContent : public views::View, | |
97 public views::ButtonListener, | |
98 public views::LinkListener { | |
99 public: | |
100 InstalledBubbleContent(Browser* browser, | |
101 const Extension* extension, | |
102 ExtensionInstalledBubble::BubbleType type, | |
103 SkBitmap* icon, | |
104 ExtensionInstalledBubble* bubble) | |
105 : browser_(browser), | |
106 extension_id_(extension->id()), | |
107 bubble_(bubble), | |
108 type_(type), | |
109 flavors_(NONE), | |
110 height_of_signin_promo_(0u), | |
111 how_to_use_(NULL), | |
112 sign_in_link_(NULL), | |
113 manage_(NULL), | |
114 manage_shortcut_(NULL) { | |
115 // The Extension Installed bubble takes on various forms, depending on the | |
116 // type of extension installed. In general, though, they are all similar: | |
117 // | |
118 // ------------------------- | |
119 // | | Heading [X] | | |
120 // | Icon | Info | | |
121 // | | Extra info | | |
122 // ------------------------- | |
123 // | |
124 // Icon and Heading are always shown (as well as the close button). | |
125 // Info is shown for browser actions, page actions and Omnibox keyword | |
126 // extensions and might list keyboard shorcut for the former two types. | |
127 // Extra info is... | |
128 // ... for other types, either a description of how to manage the extension | |
129 // or a link to configure the keybinding shortcut (if one exists). | |
130 // Extra info can include a promo for signing into sync. | |
131 | |
132 // First figure out the keybinding situation. | |
133 extensions::Command command; | |
134 bool has_keybinding = GetKeybinding(&command); | |
135 string16 key; // Keyboard shortcut or keyword to display in the bubble. | |
136 | |
137 if (extensions::sync_helper::IsSyncableExtension(extension) && | |
138 SyncPromoUI::ShouldShowSyncPromo(browser->profile())) | |
139 flavors_ |= SIGN_IN_PROMO; | |
140 | |
141 // Determine the bubble flavor we want, based on the extension type. | |
142 switch (type_) { | |
143 case ExtensionInstalledBubble::BROWSER_ACTION: | |
144 case ExtensionInstalledBubble::PAGE_ACTION: { | |
145 flavors_ |= HOW_TO_USE; | |
146 if (has_keybinding) { | |
147 flavors_ |= SHOW_KEYBINDING; | |
148 key = command.accelerator().GetShortcutText(); | |
149 } else { | |
150 // The How-To-Use text makes the bubble seem a little crowded when the | |
151 // extension has a keybinding, so the How-To-Manage text is not shown | |
152 // in those cases. | |
153 flavors_ |= HOW_TO_MANAGE; | |
154 } | |
155 break; | |
156 } | |
157 case ExtensionInstalledBubble::OMNIBOX_KEYWORD: { | |
158 flavors_ |= HOW_TO_USE | HOW_TO_MANAGE; | |
159 key = UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(extension)); | |
160 break; | |
161 } | |
162 case ExtensionInstalledBubble::GENERIC: { | |
163 break; | |
164 } | |
165 default: { | |
166 // When adding a new bubble type, the flavor needs to be set. | |
167 COMPILE_ASSERT(ExtensionInstalledBubble::GENERIC == 3, | |
168 kBubbleTypeEnumHasChangedButNotThisSwitchStatement); | |
169 break; | |
170 } | |
171 } | |
172 | |
173 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
174 const gfx::Font& font = rb.GetFont(ui::ResourceBundle::BaseFont); | |
175 | |
176 // Add the icon (for all flavors). | |
177 // Scale down to 43x43, but allow smaller icons (don't scale up). | |
178 gfx::Size size(icon->width(), icon->height()); | |
179 if (size.width() > kIconSize || size.height() > kIconSize) | |
180 size = gfx::Size(kIconSize, kIconSize); | |
181 icon_ = new views::ImageView(); | |
182 icon_->SetImageSize(size); | |
183 icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(*icon)); | |
184 AddChildView(icon_); | |
185 | |
186 // Add the heading (for all flavors). | |
187 string16 extension_name = UTF8ToUTF16(extension->name()); | |
188 base::i18n::AdjustStringForLocaleDirection(&extension_name); | |
189 heading_ = new views::Label(l10n_util::GetStringFUTF16( | |
190 IDS_EXTENSION_INSTALLED_HEADING, extension_name)); | |
191 heading_->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); | |
192 heading_->SetMultiLine(true); | |
193 heading_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
194 AddChildView(heading_); | |
195 | |
196 if (flavors_ & HOW_TO_USE) { | |
197 how_to_use_ = new views::Label(GetHowToUseDescription(key)); | |
198 how_to_use_->SetFont(font); | |
199 how_to_use_->SetMultiLine(true); | |
200 how_to_use_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
201 AddChildView(how_to_use_); | |
202 } | |
203 | |
204 if (flavors_ & SHOW_KEYBINDING) { | |
205 manage_shortcut_ = new views::Link( | |
206 l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_SHORTCUTS)); | |
207 manage_shortcut_->set_listener(this); | |
208 AddChildView(manage_shortcut_); | |
209 } | |
210 | |
211 if (flavors_ & HOW_TO_MANAGE) { | |
212 manage_ = new views::Label(l10n_util::GetStringUTF16( | |
213 #if defined(OS_CHROMEOS) | |
214 IDS_EXTENSION_INSTALLED_MANAGE_INFO_CHROMEOS)); | |
215 #else | |
216 IDS_EXTENSION_INSTALLED_MANAGE_INFO)); | |
217 #endif | |
218 manage_->SetFont(font); | |
219 manage_->SetMultiLine(true); | |
220 manage_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
221 AddChildView(manage_); | |
222 } | |
223 | |
224 if (flavors_ & SIGN_IN_PROMO) { | |
225 signin_promo_text_ = | |
226 l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_SIGNIN_PROMO); | |
227 | |
228 signin_promo_link_text_ = | |
229 l10n_util::GetStringFUTF16( | |
230 IDS_EXTENSION_INSTALLED_SIGNIN_PROMO_LINK, | |
231 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); | |
232 sign_in_link_ = new views::Link(signin_promo_link_text_); | |
233 sign_in_link_->SetFont(font); | |
234 sign_in_link_->set_listener(this); | |
235 AddChildView(sign_in_link_); | |
236 } | |
237 | |
238 // Add the Close button (for all flavors). | |
239 close_button_ = new views::ImageButton(this); | |
240 close_button_->SetImage(views::CustomButton::STATE_NORMAL, | |
241 rb.GetImageSkiaNamed(IDR_CLOSE_2)); | |
242 close_button_->SetImage(views::CustomButton::STATE_HOVERED, | |
243 rb.GetImageSkiaNamed(IDR_CLOSE_2_H)); | |
244 close_button_->SetImage(views::CustomButton::STATE_PRESSED, | |
245 rb.GetImageSkiaNamed(IDR_CLOSE_2_P)); | |
246 AddChildView(close_button_); | |
247 } | |
248 | |
249 virtual void ButtonPressed(views::Button* sender, | |
250 const ui::Event& event) OVERRIDE { | |
251 if (sender == close_button_) | |
252 bubble_->StartFade(false); | |
253 else | |
254 NOTREACHED() << "Unknown view"; | |
255 } | |
256 | |
257 // Implements the views::LinkListener interface. | |
258 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE { | |
259 GetWidget()->Close(); | |
260 std::string configure_url; | |
261 if (source == manage_shortcut_) { | |
262 configure_url = chrome::kChromeUIExtensionsURL; | |
263 configure_url += chrome::kExtensionConfigureCommandsSubPage; | |
264 } else if (source == sign_in_link_) { | |
265 configure_url = signin::GetPromoURL( | |
266 signin::SOURCE_EXTENSION_INSTALL_BUBBLE, false).spec(); | |
267 } else { | |
268 NOTREACHED(); | |
269 return; | |
270 } | |
271 chrome::NavigateParams params( | |
272 chrome::GetSingletonTabNavigateParams( | |
273 browser_, GURL(configure_url.c_str()))); | |
274 chrome::Navigate(¶ms); | |
275 } | |
276 | |
277 private: | |
278 enum Flavors { | |
279 NONE = 0, | |
280 HOW_TO_USE = 1 << 0, | |
281 HOW_TO_MANAGE = 1 << 1, | |
282 SHOW_KEYBINDING = 1 << 2, | |
283 SIGN_IN_PROMO = 1 << 3, | |
284 }; | |
285 | |
286 bool GetKeybinding(extensions::Command* command) { | |
287 extensions::CommandService* command_service = | |
288 extensions::CommandService::Get(browser_->profile()); | |
289 if (type_ == ExtensionInstalledBubble::BROWSER_ACTION) { | |
290 return command_service->GetBrowserActionCommand( | |
291 extension_id_, | |
292 extensions::CommandService::ACTIVE_ONLY, | |
293 command, | |
294 NULL); | |
295 } else if (type_ == ExtensionInstalledBubble::PAGE_ACTION) { | |
296 return command_service->GetPageActionCommand( | |
297 extension_id_, | |
298 extensions::CommandService::ACTIVE_ONLY, | |
299 command, | |
300 NULL); | |
301 } else { | |
302 return false; | |
303 } | |
304 } | |
305 | |
306 string16 GetHowToUseDescription(const string16& key) { | |
307 switch (type_) { | |
308 case ExtensionInstalledBubble::BROWSER_ACTION: | |
309 if (!key.empty()) { | |
310 return l10n_util::GetStringFUTF16( | |
311 IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT, key); | |
312 } else { | |
313 return l10n_util::GetStringUTF16( | |
314 IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO); | |
315 } | |
316 break; | |
317 case ExtensionInstalledBubble::PAGE_ACTION: | |
318 if (!key.empty()) { | |
319 return l10n_util::GetStringFUTF16( | |
320 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT, key); | |
321 } else { | |
322 return l10n_util::GetStringUTF16( | |
323 IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO); | |
324 } | |
325 break; | |
326 case ExtensionInstalledBubble::OMNIBOX_KEYWORD: | |
327 return l10n_util::GetStringFUTF16( | |
328 IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO, key); | |
329 break; | |
330 default: | |
331 NOTREACHED(); | |
332 break; | |
333 } | |
334 return string16(); | |
335 } | |
336 | |
337 // Layout the signin promo at coordinates |offset_x| and |offset_y|. Returns | |
338 // the height (in pixels) of the promo UI. | |
339 int LayoutSigninPromo(int offset_x, int offset_y) { | |
340 sign_in_promo_lines_.clear(); | |
341 int height = 0; | |
342 gfx::Rect contents_area = GetContentsBounds(); | |
343 if (contents_area.IsEmpty()) | |
344 return height; | |
345 contents_area.set_width(kRightColumnWidth); | |
346 | |
347 string16 full_text = signin_promo_link_text_ + signin_promo_text_; | |
348 | |
349 // The link is the first item in the text. | |
350 const gfx::Size link_size = sign_in_link_->GetPreferredSize(); | |
351 sign_in_link_->SetBounds( | |
352 offset_x, offset_y, link_size.width(), link_size.height()); | |
353 | |
354 // Word-wrap the full label text. | |
355 const gfx::Font font; | |
356 std::vector<string16> lines; | |
357 ui::ElideRectangleText(full_text, font, contents_area.width(), | |
358 contents_area.height(), ui::ELIDE_LONG_WORDS, | |
359 &lines); | |
360 | |
361 gfx::Point position = gfx::Point( | |
362 contents_area.origin().x() + offset_x, | |
363 contents_area.origin().y() + offset_y + 1); | |
364 if (base::i18n::IsRTL()) { | |
365 position -= gfx::Vector2d( | |
366 2 * views::kPanelHorizMargin + kHorizOuterMargin, 0); | |
367 } | |
368 | |
369 // Loop through the lines, creating a renderer for each. | |
370 for (std::vector<string16>::const_iterator it = lines.begin(); | |
371 it != lines.end(); ++it) { | |
372 gfx::RenderText* line = gfx::RenderText::CreateInstance(); | |
373 line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI); | |
374 line->SetText(*it); | |
375 const gfx::Size size(contents_area.width(), | |
376 line->GetStringSize().height()); | |
377 line->SetDisplayRect(gfx::Rect(position, size)); | |
378 position.set_y(position.y() + size.height()); | |
379 sign_in_promo_lines_.push_back(line); | |
380 height += size.height(); | |
381 } | |
382 | |
383 // The link is drawn separately; make it transparent here to only draw once. | |
384 // The link always leads other text and is assumed to fit on the first line. | |
385 sign_in_promo_lines_.front()->ApplyColor(SK_ColorTRANSPARENT, | |
386 ui::Range(0, signin_promo_link_text_.size())); | |
387 | |
388 return height; | |
389 } | |
390 | |
391 virtual gfx::Size GetPreferredSize() OVERRIDE { | |
392 int width = kHorizOuterMargin; | |
393 width += kIconSize; | |
394 width += views::kPanelHorizMargin; | |
395 width += kRightColumnWidth; | |
396 width += 2 * views::kPanelHorizMargin; | |
397 width += kHorizOuterMargin; | |
398 | |
399 int height = kVertOuterMargin; | |
400 height += heading_->GetHeightForWidth(kRightColumnWidth); | |
401 height += kVertInnerMargin; | |
402 | |
403 if (flavors_ & HOW_TO_USE) { | |
404 height += how_to_use_->GetHeightForWidth(kRightColumnWidth); | |
405 height += kVertInnerMargin; | |
406 } | |
407 | |
408 if (flavors_ & HOW_TO_MANAGE) { | |
409 height += manage_->GetHeightForWidth(kRightColumnWidth); | |
410 height += kVertInnerMargin; | |
411 } | |
412 | |
413 if (flavors_ & SIGN_IN_PROMO && height_of_signin_promo_ > 0u) { | |
414 height += height_of_signin_promo_; | |
415 height += kVertInnerMargin; | |
416 } | |
417 | |
418 if (flavors_ & SHOW_KEYBINDING) { | |
419 height += manage_shortcut_->GetHeightForWidth(kRightColumnWidth); | |
420 height += kVertInnerMargin; | |
421 } | |
422 | |
423 return gfx::Size(width, std::max(height, kIconSize + 2 * kVertOuterMargin)); | |
424 } | |
425 | |
426 virtual void Layout() OVERRIDE { | |
427 int x = kHorizOuterMargin; | |
428 int y = kVertOuterMargin; | |
429 | |
430 icon_->SetBounds(x, y, kIconSize, kIconSize); | |
431 x += kIconSize; | |
432 x += views::kPanelHorizMargin; | |
433 | |
434 y += kRightcolumnVerticalShift; | |
435 heading_->SizeToFit(kRightColumnWidth); | |
436 heading_->SetX(x); | |
437 heading_->SetY(y); | |
438 y += heading_->height(); | |
439 y += kVertInnerMargin; | |
440 | |
441 if (flavors_ & HOW_TO_USE) { | |
442 how_to_use_->SizeToFit(kRightColumnWidth); | |
443 how_to_use_->SetX(x); | |
444 how_to_use_->SetY(y); | |
445 y += how_to_use_->height(); | |
446 y += kVertInnerMargin; | |
447 } | |
448 | |
449 if (flavors_ & HOW_TO_MANAGE) { | |
450 manage_->SizeToFit(kRightColumnWidth); | |
451 manage_->SetX(x); | |
452 manage_->SetY(y); | |
453 y += manage_->height(); | |
454 y += kVertInnerMargin; | |
455 } | |
456 | |
457 if (flavors_ & SIGN_IN_PROMO) { | |
458 height_of_signin_promo_ = LayoutSigninPromo(x, y); | |
459 y += height_of_signin_promo_; | |
460 y += kVertInnerMargin; | |
461 } | |
462 | |
463 if (flavors_ & SHOW_KEYBINDING) { | |
464 gfx::Size sz = manage_shortcut_->GetPreferredSize(); | |
465 manage_shortcut_->SetBounds(width() - 2 * kHorizOuterMargin - sz.width(), | |
466 y, | |
467 sz.width(), | |
468 sz.height()); | |
469 y += manage_shortcut_->height(); | |
470 y += kVertInnerMargin; | |
471 } | |
472 | |
473 gfx::Size sz; | |
474 x += kRightColumnWidth + 2 * views::kPanelHorizMargin + kHorizOuterMargin - | |
475 close_button_->GetPreferredSize().width(); | |
476 y = kVertOuterMargin; | |
477 sz = close_button_->GetPreferredSize(); | |
478 // x-1 & y-1 is just slop to get the close button visually aligned with the | |
479 // title text and bubble arrow. | |
480 close_button_->SetBounds(x - 1, y - 1, sz.width(), sz.height()); | |
481 } | |
482 | |
483 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { | |
484 for (ScopedVector<gfx::RenderText>::const_iterator it = | |
485 sign_in_promo_lines_.begin(); | |
486 it != sign_in_promo_lines_.end(); ++it) | |
487 (*it)->Draw(canvas); | |
488 | |
489 views::View::OnPaint(canvas); | |
490 } | |
491 | |
492 // The browser we're associated with. | |
493 Browser* browser_; | |
494 | |
495 // The id of the extension just installed. | |
496 const std::string extension_id_; | |
497 | |
498 // The ExtensionInstalledBubble showing us. | |
499 ExtensionInstalledBubble* bubble_; | |
500 | |
501 // The string that contains the link text at the beginning of the sign-in | |
502 // promo text. | |
503 string16 signin_promo_link_text_; | |
504 // The remaining text of the sign-in promo text. | |
505 string16 signin_promo_text_; | |
506 | |
507 // A vector of RenderText objects representing the full sign-in promo | |
508 // paragraph as layed out within the bubble, but has the text of the link | |
509 // whited out so the link can be drawn in its place. | |
510 ScopedVector<gfx::RenderText> sign_in_promo_lines_; | |
511 | |
512 // The type of the bubble to show (Browser Action, Omnibox keyword, etc). | |
513 ExtensionInstalledBubble::BubbleType type_; | |
514 | |
515 // A bitmask containing the various flavors of bubble sections to show. | |
516 int flavors_; | |
517 | |
518 // The height, in pixels, of the sign-in promo. | |
519 size_t height_of_signin_promo_; | |
520 | |
521 views::ImageView* icon_; | |
522 views::Label* heading_; | |
523 views::Label* how_to_use_; | |
524 views::Link* sign_in_link_; | |
525 views::Label* manage_; | |
526 views::Link* manage_shortcut_; | |
527 views::ImageButton* close_button_; | |
528 | |
529 DISALLOW_COPY_AND_ASSIGN(InstalledBubbleContent); | |
530 }; | |
531 | |
532 void ExtensionInstalledBubble::Show(const Extension* extension, | |
533 Browser *browser, | |
534 const SkBitmap& icon) { | |
535 new ExtensionInstalledBubble(extension, browser, icon); | |
536 } | |
537 | |
538 ExtensionInstalledBubble::ExtensionInstalledBubble(const Extension* extension, | |
539 Browser *browser, | |
540 const SkBitmap& icon) | |
541 : extension_(extension), | |
542 browser_(browser), | |
543 icon_(icon), | |
544 animation_wait_retries_(0), | |
545 weak_factory_(this) { | |
546 if (!extensions::OmniboxInfo::GetKeyword(extension).empty()) | |
547 type_ = OMNIBOX_KEYWORD; | |
548 else if (extensions::ActionInfo::GetBrowserActionInfo(extension)) | |
549 type_ = BROWSER_ACTION; | |
550 else if (extensions::ActionInfo::GetPageActionInfo(extension) && | |
551 extensions::ActionInfo::IsVerboseInstallMessage(extension)) | |
552 type_ = PAGE_ACTION; | |
553 else | |
554 type_ = GENERIC; | |
555 | |
556 // |extension| has been initialized but not loaded at this point. We need | |
557 // to wait on showing the Bubble until not only the EXTENSION_LOADED gets | |
558 // fired, but all of the EXTENSION_LOADED Observers have run. Only then can we | |
559 // be sure that a BrowserAction or PageAction has had views created which we | |
560 // can inspect for the purpose of previewing of pointing to them. | |
561 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, | |
562 content::Source<Profile>(browser->profile())); | |
563 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, | |
564 content::Source<Profile>(browser->profile())); | |
565 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING, | |
566 content::Source<Browser>(browser)); | |
567 } | |
568 | |
569 ExtensionInstalledBubble::~ExtensionInstalledBubble() {} | |
570 | |
571 void ExtensionInstalledBubble::Observe( | |
572 int type, | |
573 const content::NotificationSource& source, | |
574 const content::NotificationDetails& details) { | |
575 if (type == chrome::NOTIFICATION_EXTENSION_LOADED) { | |
576 const Extension* extension = | |
577 content::Details<const Extension>(details).ptr(); | |
578 if (extension == extension_) { | |
579 animation_wait_retries_ = 0; | |
580 // PostTask to ourself to allow all EXTENSION_LOADED Observers to run. | |
581 base::MessageLoopForUI::current()->PostTask( | |
582 FROM_HERE, | |
583 base::Bind(&ExtensionInstalledBubble::ShowInternal, | |
584 weak_factory_.GetWeakPtr())); | |
585 } | |
586 } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { | |
587 const Extension* extension = | |
588 content::Details<extensions::UnloadedExtensionInfo>(details)->extension; | |
589 if (extension == extension_) { | |
590 // Extension is going away, make sure ShowInternal won't be called. | |
591 weak_factory_.InvalidateWeakPtrs(); | |
592 extension_ = NULL; | |
593 } | |
594 } else if (type == chrome::NOTIFICATION_BROWSER_CLOSING) { | |
595 delete this; | |
596 } else { | |
597 NOTREACHED() << L"Received unexpected notification"; | |
598 } | |
599 } | |
600 | |
601 void ExtensionInstalledBubble::ShowInternal() { | |
602 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_); | |
603 extensions::ExtensionActionManager* extension_action_manager = | |
604 extensions::ExtensionActionManager::Get(browser_->profile()); | |
605 | |
606 views::View* reference_view = NULL; | |
607 if (type_ == BROWSER_ACTION) { | |
608 BrowserActionsContainer* container = | |
609 browser_view->GetToolbarView()->browser_actions(); | |
610 if (container->animating() && | |
611 animation_wait_retries_++ < kAnimationWaitMaxRetry) { | |
612 // We don't know where the view will be until the container has stopped | |
613 // animating, so check back in a little while. | |
614 base::MessageLoopForUI::current()->PostDelayedTask( | |
615 FROM_HERE, | |
616 base::Bind(&ExtensionInstalledBubble::ShowInternal, | |
617 weak_factory_.GetWeakPtr()), | |
618 base::TimeDelta::FromMilliseconds(kAnimationWaitTime)); | |
619 return; | |
620 } | |
621 reference_view = container->GetBrowserActionView( | |
622 extension_action_manager->GetBrowserAction(*extension_)); | |
623 // If the view is not visible then it is in the chevron, so point the | |
624 // install bubble to the chevron instead. If this is an incognito window, | |
625 // both could be invisible. | |
626 if (!reference_view || !reference_view->visible()) { | |
627 reference_view = container->chevron(); | |
628 if (!reference_view || !reference_view->visible()) | |
629 reference_view = NULL; // fall back to app menu below. | |
630 } | |
631 } else if (type_ == PAGE_ACTION) { | |
632 LocationBarView* location_bar_view = browser_view->GetLocationBarView(); | |
633 ExtensionAction* page_action = | |
634 extension_action_manager->GetPageAction(*extension_); | |
635 location_bar_view->SetPreviewEnabledPageAction(page_action, | |
636 true); // preview_enabled | |
637 reference_view = location_bar_view->GetPageActionView(page_action); | |
638 DCHECK(reference_view); | |
639 } else if (type_ == OMNIBOX_KEYWORD) { | |
640 LocationBarView* location_bar_view = browser_view->GetLocationBarView(); | |
641 reference_view = location_bar_view; | |
642 DCHECK(reference_view); | |
643 } | |
644 | |
645 // Default case. | |
646 if (reference_view == NULL) | |
647 reference_view = browser_view->GetToolbarView()->app_menu(); | |
648 set_anchor_view(reference_view); | |
649 | |
650 set_arrow(type_ == OMNIBOX_KEYWORD ? views::BubbleBorder::TOP_LEFT : | |
651 views::BubbleBorder::TOP_RIGHT); | |
652 SetLayoutManager(new views::FillLayout()); | |
653 AddChildView( | |
654 new InstalledBubbleContent(browser_, extension_, type_, &icon_, this)); | |
655 | |
656 views::BubbleDelegateView::CreateBubble(this); | |
657 | |
658 // The bubble widget is now the parent and owner of |this| and takes care of | |
659 // deletion when the bubble or browser go away. | |
660 registrar_.Remove(this, chrome::NOTIFICATION_BROWSER_CLOSING, | |
661 content::Source<Browser>(browser_)); | |
662 | |
663 StartFade(true); | |
664 } | |
665 | |
666 gfx::Rect ExtensionInstalledBubble::GetAnchorRect() { | |
667 // For omnibox keyword bubbles, move the arrow to point to the left edge | |
668 // of the omnibox, just to the right of the icon. | |
669 if (type_ == OMNIBOX_KEYWORD) { | |
670 LocationBarView* location_bar_view = | |
671 BrowserView::GetBrowserViewForBrowser(browser_)->GetLocationBarView(); | |
672 return gfx::Rect(location_bar_view->GetLocationEntryOrigin(), | |
673 gfx::Size(0, location_bar_view->location_entry_view()->height())); | |
674 } | |
675 return views::BubbleDelegateView::GetAnchorRect(); | |
676 } | |
677 | |
678 void ExtensionInstalledBubble::WindowClosing() { | |
679 if (extension_ && type_ == PAGE_ACTION) { | |
680 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_); | |
681 browser_view->GetLocationBarView()->SetPreviewEnabledPageAction( | |
682 extensions::ExtensionActionManager::Get(browser_->profile())-> | |
683 GetPageAction(*extension_), | |
684 false); // preview_enabled | |
685 } | |
686 } | |
OLD | NEW |