• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/gtk/tabs/tab_renderer_gtk.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/defaults.h"
12 #include "chrome/browser/extensions/extension_tab_helper.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
16 #include "chrome/browser/ui/gtk/custom_button.h"
17 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
18 #include "chrome/browser/ui/gtk/gtk_util.h"
19 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
20 #include "content/browser/tab_contents/tab_contents.h"
21 #include "content/common/notification_service.h"
22 #include "grit/app_resources.h"
23 #include "grit/generated_resources.h"
24 #include "grit/theme_resources.h"
25 #include "ui/base/animation/slide_animation.h"
26 #include "ui/base/animation/throb_animation.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/resource/resource_bundle.h"
29 #include "ui/gfx/canvas_skia_paint.h"
30 #include "ui/gfx/favicon_size.h"
31 #include "ui/gfx/platform_font_gtk.h"
32 #include "ui/gfx/skbitmap_operations.h"
33 
34 namespace {
35 
36 const int kFontPixelSize = 12;
37 const int kLeftPadding = 16;
38 const int kTopPadding = 6;
39 const int kRightPadding = 15;
40 const int kBottomPadding = 5;
41 const int kDropShadowHeight = 2;
42 const int kFaviconTitleSpacing = 4;
43 const int kTitleCloseButtonSpacing = 5;
44 const int kStandardTitleWidth = 175;
45 const int kDropShadowOffset = 2;
46 const int kInactiveTabBackgroundOffsetY = 15;
47 
48 // When a non-mini-tab becomes a mini-tab the width of the tab animates. If
49 // the width of a mini-tab is >= kMiniTabRendererAsNormalTabWidth then the tab
50 // is rendered as a normal tab. This is done to avoid having the title
51 // immediately disappear when transitioning a tab from normal to mini-tab.
52 const int kMiniTabRendererAsNormalTabWidth =
53     browser_defaults::kMiniTabWidth + 30;
54 
55 // The tab images are designed to overlap the toolbar by 1 pixel. For now we
56 // don't actually overlap the toolbar, so this is used to know how many pixels
57 // at the bottom of the tab images are to be ignored.
58 const int kToolbarOverlap = 1;
59 
60 // How long the hover state takes.
61 const int kHoverDurationMs = 90;
62 
63 // How opaque to make the hover state (out of 1).
64 const double kHoverOpacity = 0.33;
65 
66 // Max opacity for the mini-tab title change animation.
67 const double kMiniTitleChangeThrobOpacity = 0.75;
68 
69 // Duration for when the title of an inactive mini-tab changes.
70 const int kMiniTitleChangeThrobDuration = 1000;
71 
72 const SkScalar kTabCapWidth = 15;
73 const SkScalar kTabTopCurveWidth = 4;
74 const SkScalar kTabBottomCurveWidth = 3;
75 
76 // The vertical and horizontal offset used to position the close button
77 // in the tab. TODO(jhawkins): Ask pkasting what the Fuzz is about.
78 const int kCloseButtonVertFuzz = 0;
79 const int kCloseButtonHorzFuzz = 5;
80 
81 SkBitmap* crashed_favicon = NULL;
82 
83 // Gets the bounds of |widget| relative to |parent|.
GetWidgetBoundsRelativeToParent(GtkWidget * parent,GtkWidget * widget)84 gfx::Rect GetWidgetBoundsRelativeToParent(GtkWidget* parent,
85                                           GtkWidget* widget) {
86   gfx::Point parent_pos = gtk_util::GetWidgetScreenPosition(parent);
87   gfx::Point widget_pos = gtk_util::GetWidgetScreenPosition(widget);
88   return gfx::Rect(widget_pos.x() - parent_pos.x(),
89                    widget_pos.y() - parent_pos.y(),
90                    widget->allocation.width, widget->allocation.height);
91 }
92 
93 }  // namespace
94 
Data(ui::ThemeProvider * theme_provider)95 TabRendererGtk::LoadingAnimation::Data::Data(
96     ui::ThemeProvider* theme_provider) {
97   // The loading animation image is a strip of states. Each state must be
98   // square, so the height must divide the width evenly.
99   loading_animation_frames = theme_provider->GetBitmapNamed(IDR_THROBBER);
100   DCHECK(loading_animation_frames);
101   DCHECK_EQ(loading_animation_frames->width() %
102             loading_animation_frames->height(), 0);
103   loading_animation_frame_count =
104       loading_animation_frames->width() /
105       loading_animation_frames->height();
106 
107   waiting_animation_frames =
108       theme_provider->GetBitmapNamed(IDR_THROBBER_WAITING);
109   DCHECK(waiting_animation_frames);
110   DCHECK_EQ(waiting_animation_frames->width() %
111             waiting_animation_frames->height(), 0);
112   waiting_animation_frame_count =
113       waiting_animation_frames->width() /
114       waiting_animation_frames->height();
115 
116   waiting_to_loading_frame_count_ratio =
117       waiting_animation_frame_count /
118       loading_animation_frame_count;
119   // TODO(beng): eventually remove this when we have a proper themeing system.
120   //             themes not supporting IDR_THROBBER_WAITING are causing this
121   //             value to be 0 which causes DIV0 crashes. The value of 5
122   //             matches the current bitmaps in our source.
123   if (waiting_to_loading_frame_count_ratio == 0)
124     waiting_to_loading_frame_count_ratio = 5;
125 }
126 
Data(int loading,int waiting,int waiting_to_loading)127 TabRendererGtk::LoadingAnimation::Data::Data(
128     int loading, int waiting, int waiting_to_loading)
129     : waiting_animation_frames(NULL),
130       loading_animation_frames(NULL),
131       loading_animation_frame_count(loading),
132       waiting_animation_frame_count(waiting),
133       waiting_to_loading_frame_count_ratio(waiting_to_loading) {
134 }
135 
136 bool TabRendererGtk::initialized_ = false;
137 TabRendererGtk::TabImage TabRendererGtk::tab_active_ = {0};
138 TabRendererGtk::TabImage TabRendererGtk::tab_inactive_ = {0};
139 TabRendererGtk::TabImage TabRendererGtk::tab_alpha_ = {0};
140 gfx::Font* TabRendererGtk::title_font_ = NULL;
141 int TabRendererGtk::title_font_height_ = 0;
142 int TabRendererGtk::close_button_width_ = 0;
143 int TabRendererGtk::close_button_height_ = 0;
144 SkColor TabRendererGtk::selected_title_color_ = SK_ColorBLACK;
145 SkColor TabRendererGtk::unselected_title_color_ = SkColorSetRGB(64, 64, 64);
146 
147 ////////////////////////////////////////////////////////////////////////////////
148 // TabRendererGtk::LoadingAnimation, public:
149 //
LoadingAnimation(ui::ThemeProvider * theme_provider)150 TabRendererGtk::LoadingAnimation::LoadingAnimation(
151     ui::ThemeProvider* theme_provider)
152     : data_(new Data(theme_provider)),
153       theme_service_(theme_provider),
154       animation_state_(ANIMATION_NONE),
155       animation_frame_(0) {
156   registrar_.Add(this,
157                  NotificationType::BROWSER_THEME_CHANGED,
158                  NotificationService::AllSources());
159 }
160 
LoadingAnimation(const LoadingAnimation::Data & data)161 TabRendererGtk::LoadingAnimation::LoadingAnimation(
162     const LoadingAnimation::Data& data)
163     : data_(new Data(data)),
164       theme_service_(NULL),
165       animation_state_(ANIMATION_NONE),
166       animation_frame_(0) {
167 }
168 
~LoadingAnimation()169 TabRendererGtk::LoadingAnimation::~LoadingAnimation() {}
170 
ValidateLoadingAnimation(AnimationState animation_state)171 bool TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation(
172     AnimationState animation_state) {
173   bool has_changed = false;
174   if (animation_state_ != animation_state) {
175     // The waiting animation is the reverse of the loading animation, but at a
176     // different rate - the following reverses and scales the animation_frame_
177     // so that the frame is at an equivalent position when going from one
178     // animation to the other.
179     if (animation_state_ == ANIMATION_WAITING &&
180         animation_state == ANIMATION_LOADING) {
181       animation_frame_ = data_->loading_animation_frame_count -
182           (animation_frame_ / data_->waiting_to_loading_frame_count_ratio);
183     }
184     animation_state_ = animation_state;
185     has_changed = true;
186   }
187 
188   if (animation_state_ != ANIMATION_NONE) {
189     animation_frame_ = (animation_frame_ + 1) %
190                        ((animation_state_ == ANIMATION_WAITING) ?
191                          data_->waiting_animation_frame_count :
192                          data_->loading_animation_frame_count);
193     has_changed = true;
194   } else {
195     animation_frame_ = 0;
196   }
197   return has_changed;
198 }
199 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)200 void TabRendererGtk::LoadingAnimation::Observe(
201     NotificationType type,
202     const NotificationSource& source,
203     const NotificationDetails& details) {
204   DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
205   data_.reset(new Data(theme_service_));
206 }
207 
208 ////////////////////////////////////////////////////////////////////////////////
209 // FaviconCrashAnimation
210 //
211 //  A custom animation subclass to manage the favicon crash animation.
212 class TabRendererGtk::FaviconCrashAnimation : public ui::LinearAnimation,
213                                               public ui::AnimationDelegate {
214  public:
FaviconCrashAnimation(TabRendererGtk * target)215   explicit FaviconCrashAnimation(TabRendererGtk* target)
216       : ALLOW_THIS_IN_INITIALIZER_LIST(ui::LinearAnimation(1000, 25, this)),
217         target_(target) {
218   }
~FaviconCrashAnimation()219   virtual ~FaviconCrashAnimation() {}
220 
221   // ui::Animation overrides:
AnimateToState(double state)222   virtual void AnimateToState(double state) {
223     const double kHidingOffset = 27;
224 
225     if (state < .5) {
226       target_->SetFaviconHidingOffset(
227           static_cast<int>(floor(kHidingOffset * 2.0 * state)));
228     } else {
229       target_->DisplayCrashedFavicon();
230       target_->SetFaviconHidingOffset(
231           static_cast<int>(
232               floor(kHidingOffset - ((state - .5) * 2.0 * kHidingOffset))));
233     }
234   }
235 
236   // ui::AnimationDelegate overrides:
AnimationCanceled(const ui::Animation * animation)237   virtual void AnimationCanceled(const ui::Animation* animation) {
238     target_->SetFaviconHidingOffset(0);
239   }
240 
241  private:
242   TabRendererGtk* target_;
243 
244   DISALLOW_COPY_AND_ASSIGN(FaviconCrashAnimation);
245 };
246 
247 ////////////////////////////////////////////////////////////////////////////////
248 // TabRendererGtk, public:
249 
TabRendererGtk(ui::ThemeProvider * theme_provider)250 TabRendererGtk::TabRendererGtk(ui::ThemeProvider* theme_provider)
251     : showing_icon_(false),
252       showing_close_button_(false),
253       favicon_hiding_offset_(0),
254       should_display_crashed_favicon_(false),
255       loading_animation_(theme_provider),
256       background_offset_x_(0),
257       background_offset_y_(kInactiveTabBackgroundOffsetY),
258       close_button_color_(0) {
259   InitResources();
260 
261   tab_.Own(gtk_fixed_new());
262   gtk_widget_set_app_paintable(tab_.get(), TRUE);
263   g_signal_connect(tab_.get(), "expose-event",
264                    G_CALLBACK(OnExposeEventThunk), this);
265   g_signal_connect(tab_.get(), "size-allocate",
266                    G_CALLBACK(OnSizeAllocateThunk), this);
267   close_button_.reset(MakeCloseButton());
268   gtk_widget_show(tab_.get());
269 
270   hover_animation_.reset(new ui::SlideAnimation(this));
271   hover_animation_->SetSlideDuration(kHoverDurationMs);
272 
273   registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
274                  NotificationService::AllSources());
275 }
276 
~TabRendererGtk()277 TabRendererGtk::~TabRendererGtk() {
278   tab_.Destroy();
279   for (BitmapCache::iterator it = cached_bitmaps_.begin();
280        it != cached_bitmaps_.end(); ++it) {
281     delete it->second.bitmap;
282   }
283 }
284 
UpdateData(TabContents * contents,bool app,bool loading_only)285 void TabRendererGtk::UpdateData(TabContents* contents,
286                                 bool app,
287                                 bool loading_only) {
288   DCHECK(contents);
289   theme_service_ = GtkThemeService::GetFrom(contents->profile());
290 
291   if (!loading_only) {
292     data_.title = contents->GetTitle();
293     data_.incognito = contents->profile()->IsOffTheRecord();
294     data_.crashed = contents->is_crashed();
295 
296     SkBitmap* app_icon =
297         TabContentsWrapper::GetCurrentWrapperForContents(contents)->
298             extension_tab_helper()->GetExtensionAppIcon();
299     if (app_icon)
300       data_.favicon = *app_icon;
301     else
302       data_.favicon = contents->GetFavicon();
303 
304     data_.app = app;
305     // This is kind of a hacky way to determine whether our icon is the default
306     // favicon. But the plumbing that would be necessary to do it right would
307     // be a good bit of work and would sully code for other platforms which
308     // don't care to custom-theme the favicon. Hopefully the default favicon
309     // will eventually be chromium-themable and this code will go away.
310     data_.is_default_favicon =
311         (data_.favicon.pixelRef() ==
312         ResourceBundle::GetSharedInstance().GetBitmapNamed(
313             IDR_DEFAULT_FAVICON)->pixelRef());
314   }
315 
316   // Loading state also involves whether we show the favicon, since that's where
317   // we display the throbber.
318   data_.loading = contents->is_loading();
319   data_.show_icon = contents->ShouldDisplayFavicon();
320 }
321 
UpdateFromModel()322 void TabRendererGtk::UpdateFromModel() {
323   // Force a layout, since the tab may have grown a favicon.
324   Layout();
325   SchedulePaint();
326 
327   if (data_.crashed) {
328     if (!should_display_crashed_favicon_ && !IsPerformingCrashAnimation())
329       StartCrashAnimation();
330   } else {
331     if (IsPerformingCrashAnimation())
332       StopCrashAnimation();
333     ResetCrashedFavicon();
334   }
335 }
336 
SetBlocked(bool blocked)337 void TabRendererGtk::SetBlocked(bool blocked) {
338   if (data_.blocked == blocked)
339     return;
340   data_.blocked = blocked;
341   // TODO(zelidrag) bug 32399: Make tabs pulse on Linux as well.
342 }
343 
is_blocked() const344 bool TabRendererGtk::is_blocked() const {
345   return data_.blocked;
346 }
347 
IsSelected() const348 bool TabRendererGtk::IsSelected() const {
349   return true;
350 }
351 
IsVisible() const352 bool TabRendererGtk::IsVisible() const {
353   return GTK_WIDGET_FLAGS(tab_.get()) & GTK_VISIBLE;
354 }
355 
SetVisible(bool visible) const356 void TabRendererGtk::SetVisible(bool visible) const {
357   if (visible) {
358     gtk_widget_show(tab_.get());
359     if (data_.mini)
360       gtk_widget_show(close_button_->widget());
361   } else {
362     gtk_widget_hide_all(tab_.get());
363   }
364 }
365 
ValidateLoadingAnimation(AnimationState animation_state)366 bool TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) {
367   return loading_animation_.ValidateLoadingAnimation(animation_state);
368 }
369 
PaintFaviconArea(GdkEventExpose * event)370 void TabRendererGtk::PaintFaviconArea(GdkEventExpose* event) {
371   DCHECK(ShouldShowIcon());
372 
373   // The paint area is the favicon bounds, but we're painting into the gdk
374   // window belonging to the tabstrip.  So the coordinates are relative to the
375   // top left of the tab strip.
376   event->area.x = x() + favicon_bounds_.x();
377   event->area.y = y() + favicon_bounds_.y();
378   event->area.width = favicon_bounds_.width();
379   event->area.height = favicon_bounds_.height();
380   gfx::CanvasSkiaPaint canvas(event, false);
381 
382   // The actual paint methods expect 0, 0 to be the tab top left (see
383   // PaintTab).
384   canvas.TranslateInt(x(), y());
385 
386   // Paint the background behind the favicon.
387   int theme_id;
388   int offset_y = 0;
389   if (IsSelected()) {
390     theme_id = IDR_THEME_TOOLBAR;
391   } else {
392     if (!data_.incognito) {
393       theme_id = IDR_THEME_TAB_BACKGROUND;
394     } else {
395       theme_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
396     }
397     if (!theme_service_->HasCustomImage(theme_id))
398       offset_y = background_offset_y_;
399   }
400   SkBitmap* tab_bg = theme_service_->GetBitmapNamed(theme_id);
401   canvas.TileImageInt(*tab_bg,
402       x() + favicon_bounds_.x(), offset_y + favicon_bounds_.y(),
403       favicon_bounds_.x(), favicon_bounds_.y(),
404       favicon_bounds_.width(), favicon_bounds_.height());
405 
406   if (!IsSelected()) {
407     double throb_value = GetThrobValue();
408     if (throb_value > 0) {
409       SkRect bounds;
410       bounds.set(favicon_bounds_.x(), favicon_bounds_.y(),
411           favicon_bounds_.right(), favicon_bounds_.bottom());
412       canvas.saveLayerAlpha(&bounds, static_cast<int>(throb_value * 0xff),
413                             SkCanvas::kARGB_ClipLayer_SaveFlag);
414       canvas.drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode);
415       SkBitmap* active_bg = theme_service_->GetBitmapNamed(IDR_THEME_TOOLBAR);
416       canvas.TileImageInt(*active_bg,
417           x() + favicon_bounds_.x(), favicon_bounds_.y(),
418           favicon_bounds_.x(), favicon_bounds_.y(),
419           favicon_bounds_.width(), favicon_bounds_.height());
420       canvas.restore();
421     }
422   }
423 
424   // Now paint the icon.
425   PaintIcon(&canvas);
426 }
427 
ShouldShowIcon() const428 bool TabRendererGtk::ShouldShowIcon() const {
429   if (mini() && height() >= GetMinimumUnselectedSize().height()) {
430     return true;
431   } else if (!data_.show_icon) {
432     return false;
433   } else if (IsSelected()) {
434     // The selected tab clips favicon before close button.
435     return IconCapacity() >= 2;
436   }
437   // Non-selected tabs clip close button before favicon.
438   return IconCapacity() >= 1;
439 }
440 
441 // static
GetMinimumUnselectedSize()442 gfx::Size TabRendererGtk::GetMinimumUnselectedSize() {
443   InitResources();
444 
445   gfx::Size minimum_size;
446   minimum_size.set_width(kLeftPadding + kRightPadding);
447   // Since we use bitmap images, the real minimum height of the image is
448   // defined most accurately by the height of the end cap images.
449   minimum_size.set_height(tab_active_.image_l->height() - kToolbarOverlap);
450   return minimum_size;
451 }
452 
453 // static
GetMinimumSelectedSize()454 gfx::Size TabRendererGtk::GetMinimumSelectedSize() {
455   gfx::Size minimum_size = GetMinimumUnselectedSize();
456   minimum_size.set_width(kLeftPadding + kFaviconSize + kRightPadding);
457   return minimum_size;
458 }
459 
460 // static
GetStandardSize()461 gfx::Size TabRendererGtk::GetStandardSize() {
462   gfx::Size standard_size = GetMinimumUnselectedSize();
463   standard_size.Enlarge(kFaviconTitleSpacing + kStandardTitleWidth, 0);
464   return standard_size;
465 }
466 
467 // static
GetMiniWidth()468 int TabRendererGtk::GetMiniWidth() {
469   return browser_defaults::kMiniTabWidth;
470 }
471 
472 // static
GetContentHeight()473 int TabRendererGtk::GetContentHeight() {
474   // The height of the content of the Tab is the largest of the favicon,
475   // the title text and the close button graphic.
476   int content_height = std::max(kFaviconSize, title_font_height_);
477   return std::max(content_height, close_button_height_);
478 }
479 
480 // static
LoadTabImages()481 void TabRendererGtk::LoadTabImages() {
482   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
483 
484   tab_alpha_.image_l = rb.GetBitmapNamed(IDR_TAB_ALPHA_LEFT);
485   tab_alpha_.image_r = rb.GetBitmapNamed(IDR_TAB_ALPHA_RIGHT);
486 
487   tab_active_.image_l = rb.GetBitmapNamed(IDR_TAB_ACTIVE_LEFT);
488   tab_active_.image_c = rb.GetBitmapNamed(IDR_TAB_ACTIVE_CENTER);
489   tab_active_.image_r = rb.GetBitmapNamed(IDR_TAB_ACTIVE_RIGHT);
490   tab_active_.l_width = tab_active_.image_l->width();
491   tab_active_.r_width = tab_active_.image_r->width();
492 
493   tab_inactive_.image_l = rb.GetBitmapNamed(IDR_TAB_INACTIVE_LEFT);
494   tab_inactive_.image_c = rb.GetBitmapNamed(IDR_TAB_INACTIVE_CENTER);
495   tab_inactive_.image_r = rb.GetBitmapNamed(IDR_TAB_INACTIVE_RIGHT);
496   tab_inactive_.l_width = tab_inactive_.image_l->width();
497   tab_inactive_.r_width = tab_inactive_.image_r->width();
498 
499   close_button_width_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->width();
500   close_button_height_ = rb.GetBitmapNamed(IDR_TAB_CLOSE)->height();
501 }
502 
503 // static
SetSelectedTitleColor(SkColor color)504 void TabRendererGtk::SetSelectedTitleColor(SkColor color) {
505   selected_title_color_ = color;
506 }
507 
508 // static
SetUnselectedTitleColor(SkColor color)509 void TabRendererGtk::SetUnselectedTitleColor(SkColor color) {
510   unselected_title_color_ = color;
511 }
512 
GetNonMirroredBounds(GtkWidget * parent) const513 gfx::Rect TabRendererGtk::GetNonMirroredBounds(GtkWidget* parent) const {
514   // The tabstrip widget is a windowless widget so the tab widget's allocation
515   // is relative to the browser titlebar.  We need the bounds relative to the
516   // tabstrip.
517   gfx::Rect bounds = GetWidgetBoundsRelativeToParent(parent, widget());
518   bounds.set_x(gtk_util::MirroredLeftPointForRect(parent, bounds));
519   return bounds;
520 }
521 
GetRequisition() const522 gfx::Rect TabRendererGtk::GetRequisition() const {
523   return gfx::Rect(requisition_.x(), requisition_.y(),
524                    requisition_.width(), requisition_.height());
525 }
526 
StartMiniTabTitleAnimation()527 void TabRendererGtk::StartMiniTabTitleAnimation() {
528   if (!mini_title_animation_.get()) {
529     mini_title_animation_.reset(new ui::ThrobAnimation(this));
530     mini_title_animation_->SetThrobDuration(kMiniTitleChangeThrobDuration);
531   }
532 
533   if (!mini_title_animation_->is_animating()) {
534     mini_title_animation_->StartThrobbing(2);
535   } else if (mini_title_animation_->cycles_remaining() <= 2) {
536     // The title changed while we're already animating. Add at most one more
537     // cycle. This is done in an attempt to smooth out pages that continuously
538     // change the title.
539     mini_title_animation_->set_cycles_remaining(
540         mini_title_animation_->cycles_remaining() + 2);
541   }
542 }
543 
StopMiniTabTitleAnimation()544 void TabRendererGtk::StopMiniTabTitleAnimation() {
545   if (mini_title_animation_.get())
546     mini_title_animation_->Stop();
547 }
548 
SetBounds(const gfx::Rect & bounds)549 void TabRendererGtk::SetBounds(const gfx::Rect& bounds) {
550   requisition_ = bounds;
551   gtk_widget_set_size_request(tab_.get(), bounds.width(), bounds.height());
552 }
553 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)554 void TabRendererGtk::Observe(NotificationType type,
555                              const NotificationSource& source,
556                              const NotificationDetails& details) {
557   DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
558 
559   // Clear our cache when we receive a theme change notification because it
560   // contains cached bitmaps based off the previous theme.
561   for (BitmapCache::iterator it = cached_bitmaps_.begin();
562        it != cached_bitmaps_.end(); ++it) {
563     delete it->second.bitmap;
564   }
565   cached_bitmaps_.clear();
566 }
567 
568 ////////////////////////////////////////////////////////////////////////////////
569 // TabRendererGtk, protected:
570 
GetTitle() const571 string16 TabRendererGtk::GetTitle() const {
572   return data_.title;
573 }
574 
575 ///////////////////////////////////////////////////////////////////////////////
576 // TabRendererGtk, ui::AnimationDelegate implementation:
577 
AnimationProgressed(const ui::Animation * animation)578 void TabRendererGtk::AnimationProgressed(const ui::Animation* animation) {
579   gtk_widget_queue_draw(tab_.get());
580 }
581 
AnimationCanceled(const ui::Animation * animation)582 void TabRendererGtk::AnimationCanceled(const ui::Animation* animation) {
583   AnimationEnded(animation);
584 }
585 
AnimationEnded(const ui::Animation * animation)586 void TabRendererGtk::AnimationEnded(const ui::Animation* animation) {
587   gtk_widget_queue_draw(tab_.get());
588 }
589 
590 ////////////////////////////////////////////////////////////////////////////////
591 // TabRendererGtk, private:
592 
StartCrashAnimation()593 void TabRendererGtk::StartCrashAnimation() {
594   if (!crash_animation_.get())
595     crash_animation_.reset(new FaviconCrashAnimation(this));
596   crash_animation_->Stop();
597   crash_animation_->Start();
598 }
599 
StopCrashAnimation()600 void TabRendererGtk::StopCrashAnimation() {
601   if (!crash_animation_.get())
602     return;
603   crash_animation_->Stop();
604 }
605 
IsPerformingCrashAnimation() const606 bool TabRendererGtk::IsPerformingCrashAnimation() const {
607   return crash_animation_.get() && crash_animation_->is_animating();
608 }
609 
SetFaviconHidingOffset(int offset)610 void TabRendererGtk::SetFaviconHidingOffset(int offset) {
611   favicon_hiding_offset_ = offset;
612   SchedulePaint();
613 }
614 
DisplayCrashedFavicon()615 void TabRendererGtk::DisplayCrashedFavicon() {
616   should_display_crashed_favicon_ = true;
617 }
618 
ResetCrashedFavicon()619 void TabRendererGtk::ResetCrashedFavicon() {
620   should_display_crashed_favicon_ = false;
621 }
622 
Paint(gfx::Canvas * canvas)623 void TabRendererGtk::Paint(gfx::Canvas* canvas) {
624   // Don't paint if we're narrower than we can render correctly. (This should
625   // only happen during animations).
626   if (width() < GetMinimumUnselectedSize().width() && !mini())
627     return;
628 
629   // See if the model changes whether the icons should be painted.
630   const bool show_icon = ShouldShowIcon();
631   const bool show_close_button = ShouldShowCloseBox();
632   if (show_icon != showing_icon_ ||
633       show_close_button != showing_close_button_)
634     Layout();
635 
636   PaintTabBackground(canvas);
637 
638   if (!mini() || width() > kMiniTabRendererAsNormalTabWidth)
639     PaintTitle(canvas);
640 
641   if (show_icon)
642     PaintIcon(canvas);
643 }
644 
PaintBitmap()645 SkBitmap TabRendererGtk::PaintBitmap() {
646   gfx::CanvasSkia canvas(width(), height(), false);
647   Paint(&canvas);
648   return canvas.ExtractBitmap();
649 }
650 
PaintToSurface()651 cairo_surface_t* TabRendererGtk::PaintToSurface() {
652   gfx::CanvasSkia canvas(width(), height(), false);
653   Paint(&canvas);
654   return cairo_surface_reference(cairo_get_target(canvas.beginPlatformPaint()));
655 }
656 
SchedulePaint()657 void TabRendererGtk::SchedulePaint() {
658   gtk_widget_queue_draw(tab_.get());
659 }
660 
GetLocalBounds()661 gfx::Rect TabRendererGtk::GetLocalBounds() {
662   return gfx::Rect(0, 0, bounds_.width(), bounds_.height());
663 }
664 
Layout()665 void TabRendererGtk::Layout() {
666   gfx::Rect local_bounds = GetLocalBounds();
667   if (local_bounds.IsEmpty())
668     return;
669   local_bounds.Inset(kLeftPadding, kTopPadding, kRightPadding, kBottomPadding);
670 
671   // Figure out who is tallest.
672   int content_height = GetContentHeight();
673 
674   // Size the Favicon.
675   showing_icon_ = ShouldShowIcon();
676   if (showing_icon_) {
677     int favicon_top = kTopPadding + (content_height - kFaviconSize) / 2;
678     favicon_bounds_.SetRect(local_bounds.x(), favicon_top,
679                             kFaviconSize, kFaviconSize);
680     if ((mini() || data_.animating_mini_change) &&
681         bounds_.width() < kMiniTabRendererAsNormalTabWidth) {
682       int mini_delta = kMiniTabRendererAsNormalTabWidth - GetMiniWidth();
683       int ideal_delta = bounds_.width() - GetMiniWidth();
684       if (ideal_delta < mini_delta) {
685         int ideal_x = (GetMiniWidth() - kFaviconSize) / 2;
686         int x = favicon_bounds_.x() + static_cast<int>(
687             (1 - static_cast<float>(ideal_delta) /
688              static_cast<float>(mini_delta)) *
689             (ideal_x - favicon_bounds_.x()));
690         favicon_bounds_.set_x(x);
691       }
692     }
693   } else {
694     favicon_bounds_.SetRect(local_bounds.x(), local_bounds.y(), 0, 0);
695   }
696 
697   // Size the Close button.
698   showing_close_button_ = ShouldShowCloseBox();
699   if (showing_close_button_) {
700     int close_button_top =
701         kTopPadding + kCloseButtonVertFuzz +
702         (content_height - close_button_height_) / 2;
703     close_button_bounds_.SetRect(local_bounds.width() + kCloseButtonHorzFuzz,
704                                  close_button_top, close_button_width_,
705                                  close_button_height_);
706 
707     // If the close button color has changed, generate a new one.
708     if (theme_service_) {
709       SkColor tab_text_color =
710         theme_service_->GetColor(ThemeService::COLOR_TAB_TEXT);
711       if (!close_button_color_ || tab_text_color != close_button_color_) {
712         close_button_color_ = tab_text_color;
713         ResourceBundle& rb = ResourceBundle::GetSharedInstance();
714         close_button_->SetBackground(close_button_color_,
715             rb.GetBitmapNamed(IDR_TAB_CLOSE),
716             rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK));
717       }
718     }
719   } else {
720     close_button_bounds_.SetRect(0, 0, 0, 0);
721   }
722 
723   if (!mini() || width() >= kMiniTabRendererAsNormalTabWidth) {
724     // Size the Title text to fill the remaining space.
725     int title_left = favicon_bounds_.right() + kFaviconTitleSpacing;
726     int title_top = kTopPadding;
727 
728     // If the user has big fonts, the title will appear rendered too far down
729     // on the y-axis if we use the regular top padding, so we need to adjust it
730     // so that the text appears centered.
731     gfx::Size minimum_size = GetMinimumUnselectedSize();
732     int text_height = title_top + title_font_height_ + kBottomPadding;
733     if (text_height > minimum_size.height())
734       title_top -= (text_height - minimum_size.height()) / 2;
735 
736     int title_width;
737     if (close_button_bounds_.width() && close_button_bounds_.height()) {
738       title_width = std::max(close_button_bounds_.x() -
739                              kTitleCloseButtonSpacing - title_left, 0);
740     } else {
741       title_width = std::max(local_bounds.width() - title_left, 0);
742     }
743     title_bounds_.SetRect(title_left, title_top, title_width, content_height);
744   }
745 
746   favicon_bounds_.set_x(
747       gtk_util::MirroredLeftPointForRect(tab_.get(), favicon_bounds_));
748   close_button_bounds_.set_x(
749       gtk_util::MirroredLeftPointForRect(tab_.get(), close_button_bounds_));
750   title_bounds_.set_x(
751       gtk_util::MirroredLeftPointForRect(tab_.get(), title_bounds_));
752 
753   MoveCloseButtonWidget();
754 }
755 
MoveCloseButtonWidget()756 void TabRendererGtk::MoveCloseButtonWidget() {
757   if (!close_button_bounds_.IsEmpty()) {
758     gtk_fixed_move(GTK_FIXED(tab_.get()), close_button_->widget(),
759                    close_button_bounds_.x(), close_button_bounds_.y());
760     gtk_widget_show(close_button_->widget());
761   } else {
762     gtk_widget_hide(close_button_->widget());
763   }
764 }
765 
GetMaskedBitmap(const SkBitmap * mask,const SkBitmap * background,int bg_offset_x,int bg_offset_y)766 SkBitmap* TabRendererGtk::GetMaskedBitmap(const SkBitmap* mask,
767     const SkBitmap* background, int bg_offset_x, int bg_offset_y) {
768   // We store a bitmap for each mask + background pair (4 total bitmaps).  We
769   // replace the cached image if the tab has moved relative to the background.
770   BitmapCache::iterator it = cached_bitmaps_.find(std::make_pair(mask,
771                                                                  background));
772   if (it != cached_bitmaps_.end()) {
773     if (it->second.bg_offset_x == bg_offset_x &&
774         it->second.bg_offset_y == bg_offset_y) {
775       return it->second.bitmap;
776     }
777     // The background offset changed so we should re-render with the new
778     // offsets.
779     delete it->second.bitmap;
780   }
781   SkBitmap image = SkBitmapOperations::CreateTiledBitmap(
782       *background, bg_offset_x, bg_offset_y, mask->width(),
783       height() + kToolbarOverlap);
784   CachedBitmap bitmap = {
785     bg_offset_x,
786     bg_offset_y,
787     new SkBitmap(SkBitmapOperations::CreateMaskedBitmap(image, *mask))
788   };
789   cached_bitmaps_[std::make_pair(mask, background)] = bitmap;
790   return bitmap.bitmap;
791 }
792 
PaintTab(GdkEventExpose * event)793 void TabRendererGtk::PaintTab(GdkEventExpose* event) {
794   gfx::CanvasSkiaPaint canvas(event, false);
795   if (canvas.is_empty())
796     return;
797 
798   // The tab is rendered into a windowless widget whose offset is at the
799   // coordinate event->area.  Translate by these offsets so we can render at
800   // (0,0) to match Windows' rendering metrics.
801   canvas.TranslateInt(event->area.x, event->area.y);
802 
803   // Save the original x offset so we can position background images properly.
804   background_offset_x_ = event->area.x;
805 
806   Paint(&canvas);
807 }
808 
PaintTitle(gfx::Canvas * canvas)809 void TabRendererGtk::PaintTitle(gfx::Canvas* canvas) {
810   // Paint the Title.
811   string16 title = data_.title;
812   if (title.empty()) {
813     title = data_.loading ?
814         l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) :
815         TabContentsWrapper::GetDefaultTitle();
816   } else {
817     Browser::FormatTitleForDisplay(&title);
818   }
819 
820   SkColor title_color = IsSelected() ? selected_title_color_
821                                      : unselected_title_color_;
822   canvas->DrawStringInt(title, *title_font_, title_color,
823                         title_bounds_.x(), title_bounds_.y(),
824                         title_bounds_.width(), title_bounds_.height());
825 }
826 
PaintIcon(gfx::Canvas * canvas)827 void TabRendererGtk::PaintIcon(gfx::Canvas* canvas) {
828   if (loading_animation_.animation_state() != ANIMATION_NONE) {
829     PaintLoadingAnimation(canvas);
830   } else {
831     canvas->Save();
832     canvas->ClipRectInt(0, 0, width(), height() - kFaviconTitleSpacing);
833     if (should_display_crashed_favicon_) {
834       canvas->DrawBitmapInt(*crashed_favicon, 0, 0,
835                             crashed_favicon->width(),
836                             crashed_favicon->height(),
837                             favicon_bounds_.x(),
838                             favicon_bounds_.y() + favicon_hiding_offset_,
839                             kFaviconSize, kFaviconSize,
840                             true);
841     } else {
842       if (!data_.favicon.isNull()) {
843         if (data_.is_default_favicon && theme_service_->UseGtkTheme()) {
844           GdkPixbuf* favicon = GtkThemeService::GetDefaultFavicon(true);
845           canvas->AsCanvasSkia()->DrawGdkPixbuf(
846               favicon, favicon_bounds_.x(),
847               favicon_bounds_.y() + favicon_hiding_offset_);
848         } else {
849           // If the favicon is an app icon, it is allowed to be drawn slightly
850           // larger than the standard favicon.
851           int faviconHeightOffset = data_.app ? -2 : 0;
852           int faviconWidthDelta = data_.app ?
853               data_.favicon.width() - kFaviconSize : 0;
854           int faviconHeightDelta = data_.app ?
855               data_.favicon.height() - kFaviconSize : 0;
856 
857           // TODO(pkasting): Use code in tab_icon_view.cc:PaintIcon() (or switch
858           // to using that class to render the favicon).
859           canvas->DrawBitmapInt(data_.favicon, 0, 0,
860                                 data_.favicon.width(),
861                                 data_.favicon.height(),
862                                 favicon_bounds_.x() - faviconWidthDelta/2,
863                                 favicon_bounds_.y() + faviconHeightOffset
864                                     - faviconHeightDelta/2
865                                     + favicon_hiding_offset_,
866                                 kFaviconSize + faviconWidthDelta,
867                                 kFaviconSize + faviconHeightDelta,
868                                 true);
869         }
870       }
871     }
872     canvas->Restore();
873   }
874 }
875 
PaintTabBackground(gfx::Canvas * canvas)876 void TabRendererGtk::PaintTabBackground(gfx::Canvas* canvas) {
877   if (IsSelected()) {
878     PaintActiveTabBackground(canvas);
879   } else {
880     PaintInactiveTabBackground(canvas);
881 
882     double throb_value = GetThrobValue();
883     if (throb_value > 0) {
884       canvas->SaveLayerAlpha(static_cast<int>(throb_value * 0xff),
885                              gfx::Rect(width(), height()));
886       canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255,
887                                        SkXfermode::kClear_Mode);
888       PaintActiveTabBackground(canvas);
889       canvas->Restore();
890     }
891   }
892 }
893 
PaintInactiveTabBackground(gfx::Canvas * canvas)894 void TabRendererGtk::PaintInactiveTabBackground(gfx::Canvas* canvas) {
895 
896   // The tab image needs to be lined up with the background image
897   // so that it feels partially transparent.
898   int offset_x = background_offset_x_;
899 
900   int tab_id = data_.incognito ?
901       IDR_THEME_TAB_BACKGROUND_INCOGNITO : IDR_THEME_TAB_BACKGROUND;
902 
903   SkBitmap* tab_bg = theme_service_->GetBitmapNamed(tab_id);
904 
905   // If the theme is providing a custom background image, then its top edge
906   // should be at the top of the tab. Otherwise, we assume that the background
907   // image is a composited foreground + frame image.
908   int offset_y = theme_service_->HasCustomImage(tab_id) ?
909       0 : background_offset_y_;
910 
911   // Draw left edge.
912   SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x,
913                                       offset_y);
914   canvas->DrawBitmapInt(*theme_l, 0, 0);
915 
916   // Draw right edge.
917   SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg,
918       offset_x + width() - tab_active_.r_width, offset_y);
919 
920   canvas->DrawBitmapInt(*theme_r, width() - theme_r->width(), 0);
921 
922   // Draw center.
923   canvas->TileImageInt(*tab_bg,
924       offset_x + tab_active_.l_width, kDropShadowOffset + offset_y,
925       tab_active_.l_width, 2,
926       width() - tab_active_.l_width - tab_active_.r_width, height() - 2);
927 
928   canvas->DrawBitmapInt(*tab_inactive_.image_l, 0, 0);
929   canvas->TileImageInt(*tab_inactive_.image_c, tab_inactive_.l_width, 0,
930       width() - tab_inactive_.l_width - tab_inactive_.r_width, height());
931   canvas->DrawBitmapInt(*tab_inactive_.image_r,
932       width() - tab_inactive_.r_width, 0);
933 }
934 
PaintActiveTabBackground(gfx::Canvas * canvas)935 void TabRendererGtk::PaintActiveTabBackground(gfx::Canvas* canvas) {
936   int offset_x = background_offset_x_;
937 
938   SkBitmap* tab_bg = theme_service_->GetBitmapNamed(IDR_THEME_TOOLBAR);
939 
940   // Draw left edge.
941   SkBitmap* theme_l = GetMaskedBitmap(tab_alpha_.image_l, tab_bg, offset_x, 0);
942   canvas->DrawBitmapInt(*theme_l, 0, 0);
943 
944   // Draw right edge.
945   SkBitmap* theme_r = GetMaskedBitmap(tab_alpha_.image_r, tab_bg,
946       offset_x + width() - tab_active_.r_width, 0);
947   canvas->DrawBitmapInt(*theme_r, width() - tab_active_.r_width, 0);
948 
949   // Draw center.
950   canvas->TileImageInt(*tab_bg,
951       offset_x + tab_active_.l_width, kDropShadowHeight,
952       tab_active_.l_width, kDropShadowHeight,
953       width() - tab_active_.l_width - tab_active_.r_width,
954       height() - kDropShadowHeight);
955 
956   canvas->DrawBitmapInt(*tab_active_.image_l, 0, 0);
957   canvas->TileImageInt(*tab_active_.image_c, tab_active_.l_width, 0,
958       width() - tab_active_.l_width - tab_active_.r_width, height());
959   canvas->DrawBitmapInt(*tab_active_.image_r, width() - tab_active_.r_width, 0);
960 }
961 
PaintLoadingAnimation(gfx::Canvas * canvas)962 void TabRendererGtk::PaintLoadingAnimation(gfx::Canvas* canvas) {
963   const SkBitmap* frames =
964       (loading_animation_.animation_state() == ANIMATION_WAITING) ?
965       loading_animation_.waiting_animation_frames() :
966       loading_animation_.loading_animation_frames();
967   const int image_size = frames->height();
968   const int image_offset = loading_animation_.animation_frame() * image_size;
969   DCHECK(image_size == favicon_bounds_.height());
970   DCHECK(image_size == favicon_bounds_.width());
971 
972   // NOTE: the clipping is a work around for 69528, it shouldn't be necessary.
973   canvas->Save();
974   canvas->ClipRectInt(
975       favicon_bounds_.x(), favicon_bounds_.y(), image_size, image_size);
976   canvas->DrawBitmapInt(*frames, image_offset, 0, image_size, image_size,
977       favicon_bounds_.x(), favicon_bounds_.y(), image_size, image_size,
978       false);
979   canvas->Restore();
980 }
981 
IconCapacity() const982 int TabRendererGtk::IconCapacity() const {
983   if (height() < GetMinimumUnselectedSize().height())
984     return 0;
985   return (width() - kLeftPadding - kRightPadding) / kFaviconSize;
986 }
987 
ShouldShowCloseBox() const988 bool TabRendererGtk::ShouldShowCloseBox() const {
989   // The selected tab never clips close button.
990   return !mini() && (IsSelected() || IconCapacity() >= 3);
991 }
992 
MakeCloseButton()993 CustomDrawButton* TabRendererGtk::MakeCloseButton() {
994   CustomDrawButton* button = new CustomDrawButton(IDR_TAB_CLOSE,
995       IDR_TAB_CLOSE_P, IDR_TAB_CLOSE_H, IDR_TAB_CLOSE);
996 
997   gtk_widget_set_tooltip_text(button->widget(),
998       l10n_util::GetStringUTF8(IDS_TOOLTIP_CLOSE_TAB).c_str());
999 
1000   g_signal_connect(button->widget(), "clicked",
1001                    G_CALLBACK(OnCloseButtonClickedThunk), this);
1002   g_signal_connect(button->widget(), "button-release-event",
1003                    G_CALLBACK(OnCloseButtonMouseReleaseThunk), this);
1004   g_signal_connect(button->widget(), "enter-notify-event",
1005                    G_CALLBACK(OnEnterNotifyEventThunk), this);
1006   g_signal_connect(button->widget(), "leave-notify-event",
1007                    G_CALLBACK(OnLeaveNotifyEventThunk), this);
1008   GTK_WIDGET_UNSET_FLAGS(button->widget(), GTK_CAN_FOCUS);
1009   gtk_fixed_put(GTK_FIXED(tab_.get()), button->widget(), 0, 0);
1010 
1011   return button;
1012 }
1013 
GetThrobValue()1014 double TabRendererGtk::GetThrobValue() {
1015   if (mini_title_animation_.get() && mini_title_animation_->is_animating()) {
1016     return mini_title_animation_->GetCurrentValue() *
1017         kMiniTitleChangeThrobOpacity;
1018   }
1019   return hover_animation_.get() ?
1020       kHoverOpacity * hover_animation_->GetCurrentValue() : 0;
1021 }
1022 
CloseButtonClicked()1023 void TabRendererGtk::CloseButtonClicked() {
1024   // Nothing to do.
1025 }
1026 
OnCloseButtonClicked(GtkWidget * widget)1027 void TabRendererGtk::OnCloseButtonClicked(GtkWidget* widget) {
1028   CloseButtonClicked();
1029 }
1030 
OnCloseButtonMouseRelease(GtkWidget * widget,GdkEventButton * event)1031 gboolean TabRendererGtk::OnCloseButtonMouseRelease(GtkWidget* widget,
1032                                                    GdkEventButton* event) {
1033   if (event->button == 2) {
1034     CloseButtonClicked();
1035     return TRUE;
1036   }
1037 
1038   return FALSE;
1039 }
1040 
OnExposeEvent(GtkWidget * widget,GdkEventExpose * event)1041 gboolean TabRendererGtk::OnExposeEvent(GtkWidget* widget,
1042                                        GdkEventExpose* event) {
1043   PaintTab(event);
1044   gtk_container_propagate_expose(GTK_CONTAINER(tab_.get()),
1045                                  close_button_->widget(), event);
1046   return TRUE;
1047 }
1048 
OnSizeAllocate(GtkWidget * widget,GtkAllocation * allocation)1049 void TabRendererGtk::OnSizeAllocate(GtkWidget* widget,
1050                                     GtkAllocation* allocation) {
1051   gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
1052                                allocation->width, allocation->height);
1053 
1054   // Nothing to do if the bounds are the same.  If we don't catch this, we'll
1055   // get an infinite loop of size-allocate signals.
1056   if (bounds_ == bounds)
1057     return;
1058 
1059   bounds_ = bounds;
1060   Layout();
1061 }
1062 
OnEnterNotifyEvent(GtkWidget * widget,GdkEventCrossing * event)1063 gboolean TabRendererGtk::OnEnterNotifyEvent(GtkWidget* widget,
1064                                             GdkEventCrossing* event) {
1065   hover_animation_->SetTweenType(ui::Tween::EASE_OUT);
1066   hover_animation_->Show();
1067   return FALSE;
1068 }
1069 
OnLeaveNotifyEvent(GtkWidget * widget,GdkEventCrossing * event)1070 gboolean TabRendererGtk::OnLeaveNotifyEvent(GtkWidget* widget,
1071                                             GdkEventCrossing* event) {
1072   hover_animation_->SetTweenType(ui::Tween::EASE_IN);
1073   hover_animation_->Hide();
1074   return FALSE;
1075 }
1076 
1077 // static
InitResources()1078 void TabRendererGtk::InitResources() {
1079   if (initialized_)
1080     return;
1081 
1082   LoadTabImages();
1083 
1084   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
1085   const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
1086   title_font_ = new gfx::Font(base_font.GetFontName(), kFontPixelSize);
1087   title_font_height_ = title_font_->GetHeight();
1088 
1089   crashed_favicon = rb.GetBitmapNamed(IDR_SAD_FAVICON);
1090 
1091   initialized_ = true;
1092 }
1093