glass_browser_frame_view.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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/frame/glass_browser_frame_view.h" 6 7#include "base/command_line.h" 8#include "base/prefs/pref_service.h" 9#include "base/strings/utf_string_conversions.h" 10#include "chrome/app/chrome_command_ids.h" 11#include "chrome/app/chrome_dll_resource.h" 12#include "chrome/browser/profiles/profile.h" 13#include "chrome/browser/themes/theme_properties.h" 14#include "chrome/browser/ui/views/avatar_label.h" 15#include "chrome/browser/ui/views/avatar_menu_button.h" 16#include "chrome/browser/ui/views/frame/browser_view.h" 17#include "chrome/browser/ui/views/tabs/tab.h" 18#include "chrome/browser/ui/views/tabs/tab_strip.h" 19#include "chrome/common/chrome_notification_types.h" 20#include "chrome/common/chrome_switches.h" 21#include "chrome/common/pref_names.h" 22#include "content/public/browser/notification_service.h" 23#include "grit/generated_resources.h" 24#include "grit/theme_resources.h" 25#include "grit/ui_resources.h" 26#include "ui/base/l10n/l10n_util.h" 27#include "ui/base/resource/resource_bundle_win.h" 28#include "ui/base/theme_provider.h" 29#include "ui/base/win/dpi.h" 30#include "ui/gfx/canvas.h" 31#include "ui/gfx/icon_util.h" 32#include "ui/gfx/image/image.h" 33#include "ui/views/controls/label.h" 34#include "ui/views/layout/layout_constants.h" 35#include "ui/views/win/hwnd_util.h" 36#include "ui/views/window/client_view.h" 37 38HICON GlassBrowserFrameView::throbber_icons_[ 39 GlassBrowserFrameView::kThrobberIconCount]; 40 41namespace { 42// There are 3 px of client edge drawn inside the outer frame borders. 43const int kNonClientBorderThickness = 3; 44// Besides the frame border, there's another 9 px of empty space atop the 45// window in restored mode, to use to drag the window around. 46const int kNonClientRestoredExtraThickness = 9; 47// In the window corners, the resize areas don't actually expand bigger, but the 48// 16 px at the end of the top and bottom edges triggers diagonal resizing. 49const int kResizeAreaCornerSize = 16; 50// The avatar ends 2 px above the bottom of the tabstrip (which, given the 51// way the tabstrip draws its bottom edge, will appear like a 1 px gap to the 52// user). 53const int kAvatarBottomSpacing = 2; 54// Space between the frame border and the left edge of the avatar. 55const int kAvatarLeftSpacing = 2; 56// Space between the right edge of the avatar and the tabstrip. 57const int kAvatarRightSpacing = -2; 58// The content left/right images have a shadow built into them. 59const int kContentEdgeShadowThickness = 2; 60// The top 3 px of the tabstrip is shadow; in maximized mode we push this off 61// the top of the screen so the tabs appear flush against the screen edge. 62const int kTabstripTopShadowThickness = 3; 63// In restored mode, the New Tab button isn't at the same height as the caption 64// buttons, but the space will look cluttered if it actually slides under them, 65// so we stop it when the gap between the two is down to 5 px. 66const int kNewTabCaptionRestoredSpacing = 5; 67// In maximized mode, where the New Tab button and the caption buttons are at 68// similar vertical coordinates, we need to reserve a larger, 16 px gap to avoid 69// looking too cluttered. 70const int kNewTabCaptionMaximizedSpacing = 16; 71// How far to indent the tabstrip from the left side of the screen when there 72// is no avatar icon. 73const int kTabStripIndent = -6; 74 75} // namespace 76 77/////////////////////////////////////////////////////////////////////////////// 78// GlassBrowserFrameView, public: 79 80GlassBrowserFrameView::GlassBrowserFrameView(BrowserFrame* frame, 81 BrowserView* browser_view) 82 : BrowserNonClientFrameView(frame, browser_view), 83 throbber_running_(false), 84 throbber_frame_(0) { 85 if (browser_view->ShouldShowWindowIcon()) 86 InitThrobberIcons(); 87 88 UpdateAvatarInfo(); 89 if (!browser_view->IsOffTheRecord()) { 90 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, 91 content::NotificationService::AllSources()); 92 } 93} 94 95GlassBrowserFrameView::~GlassBrowserFrameView() { 96} 97 98/////////////////////////////////////////////////////////////////////////////// 99// GlassBrowserFrameView, BrowserNonClientFrameView implementation: 100 101gfx::Rect GlassBrowserFrameView::GetBoundsForTabStrip( 102 views::View* tabstrip) const { 103 int minimize_button_offset = 104 std::min(frame()->GetMinimizeButtonOffset(), width()); 105 int tabstrip_x = browser_view()->ShouldShowAvatar() ? 106 (avatar_bounds_.right() + kAvatarRightSpacing) : 107 NonClientBorderThickness() + kTabStripIndent; 108 if (avatar_label()) { 109 tabstrip_x += avatar_label()->bounds().width() + 110 views::kRelatedControlHorizontalSpacing; 111 } 112 // In RTL languages, we have moved an avatar icon left by the size of window 113 // controls to prevent it from being rendered over them. So, we use its x 114 // position to move this tab strip left when maximized. Also, we can render 115 // a tab strip until the left end of this window without considering the size 116 // of window controls in RTL languages. 117 if (base::i18n::IsRTL()) { 118 if (!browser_view()->ShouldShowAvatar() && frame()->IsMaximized()) 119 tabstrip_x += avatar_bounds_.x(); 120 minimize_button_offset = width(); 121 } 122 int tabstrip_width = minimize_button_offset - tabstrip_x - 123 (frame()->IsMaximized() ? 124 kNewTabCaptionMaximizedSpacing : kNewTabCaptionRestoredSpacing); 125 return gfx::Rect(tabstrip_x, GetTabStripInsets(false).top, 126 std::max(0, tabstrip_width), 127 tabstrip->GetPreferredSize().height()); 128} 129 130BrowserNonClientFrameView::TabStripInsets 131GlassBrowserFrameView::GetTabStripInsets(bool restored) const { 132 // TODO: include OTR and caption. 133 return TabStripInsets(NonClientTopBorderHeight(restored), 0, 0); 134} 135 136int GlassBrowserFrameView::GetThemeBackgroundXInset() const { 137 return 0; 138} 139 140void GlassBrowserFrameView::UpdateThrobber(bool running) { 141 if (throbber_running_) { 142 if (running) { 143 DisplayNextThrobberFrame(); 144 } else { 145 StopThrobber(); 146 } 147 } else if (running) { 148 StartThrobber(); 149 } 150} 151 152gfx::Size GlassBrowserFrameView::GetMinimumSize() { 153 gfx::Size min_size(browser_view()->GetMinimumSize()); 154 155 // Account for the client area insets. 156 gfx::Insets insets = GetClientAreaInsets(); 157 min_size.Enlarge(insets.width(), insets.height()); 158 // Client area insets do not include the shadow thickness. 159 min_size.Enlarge(2 * kContentEdgeShadowThickness, 0); 160 161 // Ensure that the minimum width is enough to hold a tab strip with minimum 162 // width at its usual insets. 163 if (browser_view()->IsTabStripVisible()) { 164 TabStrip* tabstrip = browser_view()->tabstrip(); 165 int min_tabstrip_width = tabstrip->GetMinimumSize().width(); 166 int min_tabstrip_area_width = 167 width() - GetBoundsForTabStrip(tabstrip).width() + min_tabstrip_width; 168 min_size.set_width(std::max(min_tabstrip_area_width, min_size.width())); 169 } 170 171 return min_size; 172} 173 174/////////////////////////////////////////////////////////////////////////////// 175// GlassBrowserFrameView, views::NonClientFrameView implementation: 176 177gfx::Rect GlassBrowserFrameView::GetBoundsForClientView() const { 178 return client_view_bounds_; 179} 180 181gfx::Rect GlassBrowserFrameView::GetWindowBoundsForClientBounds( 182 const gfx::Rect& client_bounds) const { 183 HWND hwnd = views::HWNDForWidget(frame()); 184 if (!browser_view()->IsTabStripVisible() && hwnd) { 185 // If we don't have a tabstrip, we're either a popup or an app window, in 186 // which case we have a standard size non-client area and can just use 187 // AdjustWindowRectEx to obtain it. We check for a non-NULL window handle in 188 // case this gets called before the window is actually created. 189 RECT rect = client_bounds.ToRECT(); 190 AdjustWindowRectEx(&rect, GetWindowLong(hwnd, GWL_STYLE), FALSE, 191 GetWindowLong(hwnd, GWL_EXSTYLE)); 192 return gfx::Rect(rect); 193 } 194 195 gfx::Insets insets = GetClientAreaInsets(); 196 return gfx::Rect(std::max(0, client_bounds.x() - insets.left()), 197 std::max(0, client_bounds.y() - insets.top()), 198 client_bounds.width() + insets.width(), 199 client_bounds.height() + insets.height()); 200} 201 202int GlassBrowserFrameView::NonClientHitTest(const gfx::Point& point) { 203 // If the browser isn't in normal mode, we haven't customized the frame, so 204 // Windows can figure this out. If the point isn't within our bounds, then 205 // it's in the native portion of the frame, so again Windows can figure it 206 // out. 207 if (!browser_view()->IsBrowserTypeNormal() || !bounds().Contains(point)) 208 return HTNOWHERE; 209 210 // See if the point is within the avatar menu button or within the avatar 211 // label. 212 if ((avatar_button() && 213 avatar_button()->GetMirroredBounds().Contains(point)) || 214 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point))) 215 return HTCLIENT; 216 217 int frame_component = frame()->client_view()->NonClientHitTest(point); 218 219 // See if we're in the sysmenu region. We still have to check the tabstrip 220 // first so that clicks in a tab don't get treated as sysmenu clicks. 221 int nonclient_border_thickness = NonClientBorderThickness(); 222 if (gfx::Rect(nonclient_border_thickness, GetSystemMetrics(SM_CXSIZEFRAME), 223 GetSystemMetrics(SM_CXSMICON), 224 GetSystemMetrics(SM_CYSMICON)).Contains(point)) 225 return (frame_component == HTCLIENT) ? HTCLIENT : HTSYSMENU; 226 227 if (frame_component != HTNOWHERE) 228 return frame_component; 229 230 int frame_border_thickness = FrameBorderThickness(); 231 int window_component = GetHTComponentForFrame(point, frame_border_thickness, 232 nonclient_border_thickness, frame_border_thickness, 233 kResizeAreaCornerSize - frame_border_thickness, 234 frame()->widget_delegate()->CanResize()); 235 // Fall back to the caption if no other component matches. 236 return (window_component == HTNOWHERE) ? HTCAPTION : window_component; 237} 238 239/////////////////////////////////////////////////////////////////////////////// 240// GlassBrowserFrameView, views::View overrides: 241 242void GlassBrowserFrameView::OnPaint(gfx::Canvas* canvas) { 243 if (!browser_view()->IsTabStripVisible()) 244 return; // Nothing is visible, so don't bother to paint. 245 246 PaintToolbarBackground(canvas); 247 if (!frame()->IsMaximized()) 248 PaintRestoredClientEdge(canvas); 249} 250 251void GlassBrowserFrameView::Layout() { 252 LayoutAvatar(); 253 LayoutClientView(); 254} 255 256bool GlassBrowserFrameView::HitTestRect(const gfx::Rect& rect) const { 257 return (avatar_button() && 258 avatar_button()->GetMirroredBounds().Intersects(rect)) || 259 !frame()->client_view()->bounds().Intersects(rect); 260} 261 262/////////////////////////////////////////////////////////////////////////////// 263// GlassBrowserFrameView, private: 264 265int GlassBrowserFrameView::FrameBorderThickness() const { 266 return (frame()->IsMaximized() || frame()->IsFullscreen()) ? 267 0 : GetSystemMetrics(SM_CXSIZEFRAME); 268} 269 270int GlassBrowserFrameView::NonClientBorderThickness() const { 271 if (frame()->IsMaximized() || frame()->IsFullscreen()) 272 return 0; 273 274 return kNonClientBorderThickness; 275} 276 277int GlassBrowserFrameView::NonClientTopBorderHeight( 278 bool restored) const { 279 if (!restored && frame()->IsFullscreen()) 280 return 0; 281 282 // We'd like to use FrameBorderThickness() here, but the maximized Aero glass 283 // frame has a 0 frame border around most edges and a CYSIZEFRAME-thick border 284 // at the top (see AeroGlassFrame::OnGetMinMaxInfo()). 285 return ui::win::GetSystemMetricsInDIP(SM_CYSIZEFRAME) + 286 ((!restored && !frame()->ShouldLeaveOffsetNearTopBorder()) ? 287 -kTabstripTopShadowThickness : kNonClientRestoredExtraThickness); 288} 289 290void GlassBrowserFrameView::PaintToolbarBackground(gfx::Canvas* canvas) { 291 ui::ThemeProvider* tp = GetThemeProvider(); 292 293 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 294 gfx::Point toolbar_origin(toolbar_bounds.origin()); 295 View::ConvertPointToTarget(browser_view(), this, &toolbar_origin); 296 toolbar_bounds.set_origin(toolbar_origin); 297 int x = toolbar_bounds.x(); 298 int w = toolbar_bounds.width(); 299 int left_x = x - kContentEdgeShadowThickness; 300 301 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR); 302 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed( 303 IDR_CONTENT_TOP_LEFT_CORNER); 304 gfx::ImageSkia* toolbar_center = tp->GetImageSkiaNamed( 305 IDR_CONTENT_TOP_CENTER); 306 307 // Tile the toolbar image starting at the frame edge on the left and where 308 // the tabstrip is on the top. 309 int y = toolbar_bounds.y(); 310 int dest_y = y + (kFrameShadowThickness * 2); 311 canvas->TileImageInt(*theme_toolbar, 312 x + GetThemeBackgroundXInset(), 313 dest_y - GetTabStripInsets(false).top, x, 314 dest_y, w, theme_toolbar->height()); 315 316 // Draw rounded corners for the tab. 317 gfx::ImageSkia* toolbar_left_mask = 318 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER_MASK); 319 gfx::ImageSkia* toolbar_right_mask = 320 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_RIGHT_CORNER_MASK); 321 322 // We mask out the corners by using the DestinationIn transfer mode, 323 // which keeps the RGB pixels from the destination and the alpha from 324 // the source. 325 SkPaint paint; 326 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); 327 328 // Mask out the top left corner. 329 canvas->DrawImageInt(*toolbar_left_mask, left_x, y, paint); 330 331 // Mask out the top right corner. 332 int right_x = 333 x + w + kContentEdgeShadowThickness - toolbar_right_mask->width(); 334 canvas->DrawImageInt(*toolbar_right_mask, right_x, y, paint); 335 336 // Draw left edge. 337 canvas->DrawImageInt(*toolbar_left, left_x, y); 338 339 // Draw center edge. 340 canvas->TileImageInt(*toolbar_center, left_x + toolbar_left->width(), y, 341 right_x - (left_x + toolbar_left->width()), toolbar_center->height()); 342 343 // Right edge. 344 canvas->DrawImageInt(*tp->GetImageSkiaNamed(IDR_CONTENT_TOP_RIGHT_CORNER), 345 right_x, y); 346 347 // Draw the content/toolbar separator. 348 canvas->FillRect( 349 gfx::Rect(x + kClientEdgeThickness, 350 toolbar_bounds.bottom() - kClientEdgeThickness, 351 w - (2 * kClientEdgeThickness), 352 kClientEdgeThickness), 353 ThemeProperties::GetDefaultColor( 354 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 355} 356 357void GlassBrowserFrameView::PaintRestoredClientEdge(gfx::Canvas* canvas) { 358 ui::ThemeProvider* tp = GetThemeProvider(); 359 gfx::Rect client_area_bounds = CalculateClientAreaBounds(width(), height()); 360 361 // The client edges start below the toolbar upper corner images regardless 362 // of how tall the toolbar itself is. 363 int client_area_top = frame()->client_view()->y() + 364 browser_view()->GetToolbarBounds().y() + 365 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER)->height(); 366 int client_area_bottom = 367 std::max(client_area_top, height() - NonClientBorderThickness()); 368 int client_area_height = client_area_bottom - client_area_top; 369 370 // Draw the client edge images. 371 gfx::ImageSkia* right = tp->GetImageSkiaNamed(IDR_CONTENT_RIGHT_SIDE); 372 canvas->TileImageInt(*right, client_area_bounds.right(), client_area_top, 373 right->width(), client_area_height); 374 canvas->DrawImageInt( 375 *tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_RIGHT_CORNER), 376 client_area_bounds.right(), client_area_bottom); 377 gfx::ImageSkia* bottom = tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_CENTER); 378 canvas->TileImageInt(*bottom, client_area_bounds.x(), 379 client_area_bottom, client_area_bounds.width(), 380 bottom->height()); 381 gfx::ImageSkia* bottom_left = 382 tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_LEFT_CORNER); 383 canvas->DrawImageInt(*bottom_left, 384 client_area_bounds.x() - bottom_left->width(), client_area_bottom); 385 gfx::ImageSkia* left = tp->GetImageSkiaNamed(IDR_CONTENT_LEFT_SIDE); 386 canvas->TileImageInt(*left, client_area_bounds.x() - left->width(), 387 client_area_top, left->width(), client_area_height); 388 389 // Draw the toolbar color so that the client edges show the right color even 390 // where not covered by the toolbar image. NOTE: We do this after drawing the 391 // images because the images are meant to alpha-blend atop the frame whereas 392 // these rects are meant to be fully opaque, without anything overlaid. 393 SkColor toolbar_color = tp->GetColor(ThemeProperties::COLOR_TOOLBAR); 394 canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness, 395 client_area_top, kClientEdgeThickness, 396 client_area_bottom + kClientEdgeThickness - client_area_top), 397 toolbar_color); 398 canvas->FillRect(gfx::Rect(client_area_bounds.x(), client_area_bottom, 399 client_area_bounds.width(), kClientEdgeThickness), 400 toolbar_color); 401 canvas->FillRect(gfx::Rect(client_area_bounds.right(), client_area_top, 402 kClientEdgeThickness, 403 client_area_bottom + kClientEdgeThickness - client_area_top), 404 toolbar_color); 405} 406 407void GlassBrowserFrameView::LayoutAvatar() { 408 // Even though the avatar is used for both incognito and profiles we always 409 // use the incognito icon to layout the avatar button. The profile icon 410 // can be customized so we can't depend on its size to perform layout. 411 gfx::ImageSkia incognito_icon = browser_view()->GetOTRAvatarIcon(); 412 413 int avatar_x = NonClientBorderThickness() + kAvatarLeftSpacing; 414 // Move this avatar icon by the size of window controls to prevent it from 415 // being rendered over them in RTL languages. This code also needs to adjust 416 // the width of a tab strip to avoid decreasing this size twice. (See the 417 // comment in GetBoundsForTabStrip().) 418 if (base::i18n::IsRTL()) 419 avatar_x += width() - frame()->GetMinimizeButtonOffset(); 420 421 int avatar_bottom = GetTabStripInsets(false).top + 422 browser_view()->GetTabStripHeight() - kAvatarBottomSpacing; 423 int avatar_restored_y = avatar_bottom - incognito_icon.height(); 424 int avatar_y = frame()->IsMaximized() ? 425 (NonClientTopBorderHeight(false) + kTabstripTopShadowThickness) : 426 avatar_restored_y; 427 avatar_bounds_.SetRect(avatar_x, avatar_y, incognito_icon.width(), 428 browser_view()->ShouldShowAvatar() ? (avatar_bottom - avatar_y) : 0); 429 if (avatar_button()) 430 avatar_button()->SetBoundsRect(avatar_bounds_); 431 432 if (avatar_label()) { 433 gfx::Size size = avatar_label()->GetPreferredSize(); 434 int label_height = std::min(avatar_bounds_.height(), size.height()); 435 gfx::Rect label_bounds( 436 avatar_bounds_.right() + views::kRelatedControlHorizontalSpacing, 437 avatar_y + (avatar_bounds_.height() - label_height) / 2, 438 size.width(), 439 browser_view()->ShouldShowAvatar() ? size.height() : 0); 440 avatar_label()->SetBoundsRect(label_bounds); 441 } 442} 443 444void GlassBrowserFrameView::LayoutClientView() { 445 client_view_bounds_ = CalculateClientAreaBounds(width(), height()); 446} 447 448gfx::Insets GlassBrowserFrameView::GetClientAreaInsets() const { 449 if (!browser_view()->IsTabStripVisible()) 450 return gfx::Insets(); 451 452 const int top_height = NonClientTopBorderHeight(false); 453 const int border_thickness = NonClientBorderThickness(); 454 return gfx::Insets(top_height, 455 border_thickness, 456 border_thickness, 457 border_thickness); 458} 459 460gfx::Rect GlassBrowserFrameView::CalculateClientAreaBounds(int width, 461 int height) const { 462 gfx::Rect bounds(0, 0, width, height); 463 bounds.Inset(GetClientAreaInsets()); 464 return bounds; 465} 466 467void GlassBrowserFrameView::StartThrobber() { 468 if (!throbber_running_) { 469 throbber_running_ = true; 470 throbber_frame_ = 0; 471 InitThrobberIcons(); 472 SendMessage(views::HWNDForWidget(frame()), WM_SETICON, 473 static_cast<WPARAM>(ICON_SMALL), 474 reinterpret_cast<LPARAM>(throbber_icons_[throbber_frame_])); 475 } 476} 477 478void GlassBrowserFrameView::StopThrobber() { 479 if (throbber_running_) { 480 throbber_running_ = false; 481 482 HICON frame_icon = NULL; 483 484 // Check if hosted BrowserView has a window icon to use. 485 if (browser_view()->ShouldShowWindowIcon()) { 486 gfx::ImageSkia icon = browser_view()->GetWindowIcon(); 487 if (!icon.isNull()) 488 frame_icon = IconUtil::CreateHICONFromSkBitmap(*icon.bitmap()); 489 } 490 491 // Fallback to class icon. 492 if (!frame_icon) { 493 frame_icon = reinterpret_cast<HICON>(GetClassLongPtr( 494 views::HWNDForWidget(frame()), GCLP_HICONSM)); 495 } 496 497 // This will reset the small icon which we set in the throbber code. 498 // WM_SETICON with NULL icon restores the icon for title bar but not 499 // for taskbar. See http://crbug.com/29996 500 SendMessage(views::HWNDForWidget(frame()), WM_SETICON, 501 static_cast<WPARAM>(ICON_SMALL), 502 reinterpret_cast<LPARAM>(frame_icon)); 503 } 504} 505 506void GlassBrowserFrameView::DisplayNextThrobberFrame() { 507 throbber_frame_ = (throbber_frame_ + 1) % kThrobberIconCount; 508 SendMessage(views::HWNDForWidget(frame()), WM_SETICON, 509 static_cast<WPARAM>(ICON_SMALL), 510 reinterpret_cast<LPARAM>(throbber_icons_[throbber_frame_])); 511} 512 513void GlassBrowserFrameView::Observe( 514 int type, 515 const content::NotificationSource& source, 516 const content::NotificationDetails& details) { 517 switch (type) { 518 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: 519 UpdateAvatarInfo(); 520 break; 521 default: 522 NOTREACHED() << "Got a notification we didn't register for!"; 523 break; 524 } 525} 526 527// static 528void GlassBrowserFrameView::InitThrobberIcons() { 529 static bool initialized = false; 530 if (!initialized) { 531 for (int i = 0; i < kThrobberIconCount; ++i) { 532 throbber_icons_[i] = 533 ui::LoadThemeIconFromResourcesDataDLL(IDI_THROBBER_01 + i); 534 DCHECK(throbber_icons_[i]); 535 } 536 initialized = true; 537 } 538} 539