From b2204fb914f96e9cc505c640aeb7986b25124ca0 Mon Sep 17 00:00:00 2001 From: "willchan@chromium.org" Date: Mon, 6 Apr 2009 21:30:27 +0000 Subject: Implement loading animations. I've ignored the app mode and popup cases since I don't think the Linux port supports those modes yet. I also ignored RTL. I've made some minor lint/style cleanups along the way. BUG=9380 Review URL: http://codereview.chromium.org/60104 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13195 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/gtk/browser_window_gtk.cc | 41 +++++++- chrome/browser/gtk/browser_window_gtk.h | 11 +++ chrome/browser/gtk/tabs/tab_renderer_gtk.cc | 107 ++++++++++++++++++++- chrome/browser/gtk/tabs/tab_renderer_gtk.h | 96 ++++++++++++++---- .../browser/gtk/tabs/tab_renderer_gtk_unittest.cc | 77 +++++++++++++++ chrome/browser/gtk/tabs/tab_strip_gtk.cc | 23 ++++- chrome/browser/gtk/tabs/tab_strip_gtk.h | 15 ++- chrome/chrome.gyp | 3 + 8 files changed, 340 insertions(+), 33 deletions(-) create mode 100644 chrome/browser/gtk/tabs/tab_renderer_gtk_unittest.cc (limited to 'chrome') diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc index 478eda4..7613f69 100644 --- a/chrome/browser/gtk/browser_window_gtk.cc +++ b/chrome/browser/gtk/browser_window_gtk.cc @@ -13,6 +13,7 @@ #include "base/message_loop.h" #include "base/path_service.h" #include "base/string_util.h" +#include "base/time.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/browser.h" @@ -37,6 +38,9 @@ namespace { +// The number of milliseconds between loading animation frames. +const int kLoadingAnimationFrameTimeMs = 30; + const GdkColor kBorderColor = GDK_COLOR_RGB(0xbe, 0xc8, 0xd4); class DummyButtonListener : public views::ButtonListener { @@ -336,15 +340,42 @@ void BrowserWindowGtk::SelectedTabToolbarSizeChanged(bool is_animating) { void BrowserWindowGtk::UpdateTitleBar() { std::wstring title = browser_->GetCurrentPageTitle(); gtk_window_set_title(window_, WideToUTF8(title).c_str()); - if (browser_->SupportsWindowFeature(Browser::FEATURE_TITLEBAR)) { + if (ShouldShowWindowIcon()) { // If we're showing a title bar, we should update the app icon. NOTIMPLEMENTED(); } } void BrowserWindowGtk::UpdateLoadingAnimations(bool should_animate) { - // Need to implement loading animations. - // http://code.google.com/p/chromium/issues/detail?id=9380 + if (should_animate) { + if (!loading_animation_timer_.IsRunning()) { + // Loads are happening, and the timer isn't running, so start it. + loading_animation_timer_.Start( + base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), this, + &BrowserWindowGtk::LoadingAnimationCallback); + } + } else { + if (loading_animation_timer_.IsRunning()) { + loading_animation_timer_.Stop(); + // Loads are now complete, update the state if a task was scheduled. + LoadingAnimationCallback(); + } + } +} + +void BrowserWindowGtk::LoadingAnimationCallback() { + if (browser_->type() == Browser::TYPE_NORMAL) { + // Loading animations are shown in the tab for tabbed windows. We check the + // browser type instead of calling IsTabStripVisible() because the latter + // will return false for fullscreen windows, but we still need to update + // their animations (so that when they come out of fullscreen mode they'll + // be correct). + tabstrip_->UpdateLoadingAnimations(); + } else if (ShouldShowWindowIcon()) { + // ... or in the window icon area for popups and app windows. + // http://code.google.com/p/chromium/issues/detail?id=9380 + NOTIMPLEMENTED(); + } } void BrowserWindowGtk::SetStarredState(bool is_starred) { @@ -585,6 +616,10 @@ bool BrowserWindowGtk::CanClose() const { return true; } +bool BrowserWindowGtk::ShouldShowWindowIcon() const { + return browser_->SupportsWindowFeature(Browser::FEATURE_TITLEBAR); +} + void BrowserWindowGtk::ConnectAccelerators() { GtkAccelGroup* accel_group = gtk_accel_group_new(); gtk_window_add_accel_group(window_, accel_group); diff --git a/chrome/browser/gtk/browser_window_gtk.h b/chrome/browser/gtk/browser_window_gtk.h index f435202..4305dac 100644 --- a/chrome/browser/gtk/browser_window_gtk.h +++ b/chrome/browser/gtk/browser_window_gtk.h @@ -10,6 +10,7 @@ #include "base/gfx/rect.h" #include "base/scoped_ptr.h" #include "base/task.h" +#include "base/timer.h" #include "chrome/browser/browser_window.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/common/notification_observer.h" @@ -102,6 +103,8 @@ class BrowserWindowGtk : public BrowserWindow, // onbeforeunload handler that prevents us from closing. bool CanClose() const; + bool ShouldShowWindowIcon() const; + protected: virtual void DestroyBrowser(); GtkWindow* window_; @@ -135,6 +138,9 @@ class BrowserWindowGtk : public BrowserWindow, // A small shim for browser_->ExecuteCommand. void ExecuteBrowserCommand(int id); + // Callback for the loading animation(s) associated with this window. + void LoadingAnimationCallback(); + gfx::Rect bounds_; GdkWindowState state_; @@ -169,6 +175,11 @@ class BrowserWindowGtk : public BrowserWindow, // Experiment with using views for gtk. scoped_ptr experimental_widget_; + + // The timer used to update frames for the Loading Animation. + base::RepeatingTimer loading_animation_timer_; + + DISALLOW_COPY_AND_ASSIGN(BrowserWindowGtk); }; #endif // CHROME_BROWSER_GTK_BROWSER_WINDOW_GTK_H_ diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc index 1e55ae2..2e22487 100644 --- a/chrome/browser/gtk/tabs/tab_renderer_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.cc @@ -52,6 +52,41 @@ GdkFont* load_default_font() { return font; } +TabRendererGtk::LoadingAnimation::Data loading_animation_data; + +// Loads the loading animation images and data. +void InitializeLoadingAnimationData( + ResourceBundle* rb, TabRendererGtk::LoadingAnimation::Data* data) { + // The loading animation image is a strip of states. Each state must be + // square, so the height must divide the width evenly. + data->loading_animation_frames = rb->GetBitmapNamed(IDR_THROBBER); + DCHECK(data->loading_animation_frames); + DCHECK_EQ(data->loading_animation_frames->width() % + data->loading_animation_frames->height(), 0); + data->loading_animation_frame_count = + data->loading_animation_frames->width() / + data->loading_animation_frames->height(); + + data->waiting_animation_frames = + rb->GetBitmapNamed(IDR_THROBBER_WAITING); + DCHECK(data->waiting_animation_frames); + DCHECK_EQ(data->waiting_animation_frames->width() % + data->waiting_animation_frames->height(), 0); + data->waiting_animation_frame_count = + data->waiting_animation_frames->width() / + data->waiting_animation_frames->height(); + + data->waiting_to_loading_frame_count_ratio = + data->waiting_animation_frame_count / + data->loading_animation_frame_count; + // TODO(beng): eventually remove this when we have a proper themeing system. + // themes not supporting IDR_THROBBER_WAITING are causing this + // value to be 0 which causes DIV0 crashes. The value of 5 + // matches the current bitmaps in our source. + if (data->waiting_to_loading_frame_count_ratio == 0) + data->waiting_to_loading_frame_count_ratio = 5; +} + } // namespace bool TabRendererGtk::initialized_ = false; @@ -68,6 +103,38 @@ int TabRendererGtk::download_icon_width_ = 0; int TabRendererGtk::download_icon_height_ = 0; //////////////////////////////////////////////////////////////////////////////// +// TabRendererGtk::LoadingAnimation, public: +// +TabRendererGtk::LoadingAnimation::LoadingAnimation(const Data* data) + : data_(data), animation_state_(ANIMATION_NONE), animation_frame_(0) { +} + +void TabRendererGtk::LoadingAnimation::ValidateLoadingAnimation( + AnimationState animation_state) { + if (animation_state_ != animation_state) { + // The waiting animation is the reverse of the loading animation, but at a + // different rate - the following reverses and scales the animation_frame_ + // so that the frame is at an equivalent position when going from one + // animation to the other. + if (animation_state_ == ANIMATION_WAITING && + animation_state == ANIMATION_LOADING) { + animation_frame_ = data_->loading_animation_frame_count - + (animation_frame_ / data_->waiting_to_loading_frame_count_ratio); + } + animation_state_ = animation_state; + } + + if (animation_state_ != ANIMATION_NONE) { + animation_frame_ = ++animation_frame_ % + ((animation_state_ == ANIMATION_WAITING) ? + data_->waiting_animation_frame_count : + data_->loading_animation_frame_count); + } else { + animation_frame_ = 0; + } +} + +//////////////////////////////////////////////////////////////////////////////// // TabRendererGtk, public: TabRendererGtk::TabRendererGtk() @@ -76,7 +143,8 @@ TabRendererGtk::TabRendererGtk() showing_close_button_(false), fav_icon_hiding_offset_(0), should_display_crashed_favicon_(false), - hovering_(false) { + hovering_(false), + loading_animation_(&loading_animation_data) { InitResources(); } @@ -108,6 +176,10 @@ bool TabRendererGtk::IsSelected() const { return true; } +void TabRendererGtk::ValidateLoadingAnimation(AnimationState animation_state) { + loading_animation_.ValidateLoadingAnimation(animation_state); +} + // static gfx::Size TabRendererGtk::GetMinimumUnselectedSize() { InitResources(); @@ -218,9 +290,13 @@ void TabRendererGtk::Paint(ChromeCanvasPaint* canvas) { PaintTabBackground(canvas); - if (show_icon && !data_.favicon.isNull()) { - canvas->DrawBitmapInt(data_.favicon, favicon_bounds_.x(), - favicon_bounds_.y() + fav_icon_hiding_offset_); + if (show_icon) { + if (loading_animation_.animation_state() != ANIMATION_NONE) { + PaintLoadingAnimation(canvas); + } else if (!data_.favicon.isNull()) { + canvas->DrawBitmapInt(data_.favicon, favicon_bounds_.x(), + favicon_bounds_.y() + fav_icon_hiding_offset_); + } } if (show_download_icon) { @@ -380,6 +456,27 @@ void TabRendererGtk::PaintActiveTabBackground(ChromeCanvasPaint* canvas) { bounds_.y()); } +void TabRendererGtk::PaintLoadingAnimation(ChromeCanvasPaint* canvas) { + const SkBitmap* frames = + (loading_animation_.animation_state() == ANIMATION_WAITING) ? + loading_animation_.waiting_animation_frames() : + loading_animation_.loading_animation_frames(); + const int image_size = frames->height(); + const int image_offset = loading_animation_.animation_frame() * image_size; + const int dst_y = (height() - image_size) / 2; + + // Just like with the Tab's title and favicon, the position for the page + // loading animation also needs to be mirrored if the UI layout is RTL. + // TODO(willchan): Handle RTL. + // dst_x = x() + width() - kLeftPadding - image_size; + int dst_x = x() + kLeftPadding; + + + canvas->DrawBitmapInt(*frames, image_offset, 0, image_size, + image_size, dst_x, dst_y, image_size, image_size, + false); +} + int TabRendererGtk::IconCapacity() const { if (height() < GetMinimumUnselectedSize().height()) return 0; @@ -413,5 +510,7 @@ void TabRendererGtk::InitResources() { title_font_ = new ChromeFont(rb.GetFont(ResourceBundle::BaseFont)); title_font_height_ = title_font_->height(); + InitializeLoadingAnimationData(&rb, &loading_animation_data); + initialized_ = true; } diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk.h b/chrome/browser/gtk/tabs/tab_renderer_gtk.h index 3ccd3ea..07c7614 100644 --- a/chrome/browser/gtk/tabs/tab_renderer_gtk.h +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk.h @@ -15,12 +15,57 @@ namespace gfx { class Size; -} +} // namespace gfx class TabContents; class TabRendererGtk { public: + // Possible animation states. + enum AnimationState { + ANIMATION_NONE, + ANIMATION_WAITING, + ANIMATION_LOADING + }; + + class LoadingAnimation { + public: + struct Data { + SkBitmap* waiting_animation_frames; + SkBitmap* loading_animation_frames; + int loading_animation_frame_count; + int waiting_animation_frame_count; + int waiting_to_loading_frame_count_ratio; + }; + + explicit LoadingAnimation(const Data* data); + + // Advance the loading animation to the next frame, or hide the animation if + // the tab isn't loading. + void ValidateLoadingAnimation(AnimationState animation_state); + + AnimationState animation_state() const { return animation_state_; } + int animation_frame() const { return animation_frame_; } + + const SkBitmap* waiting_animation_frames() const { + return data_->waiting_animation_frames; + } + const SkBitmap* loading_animation_frames() const { + return data_->loading_animation_frames; + } + + private: + const Data* const data_; + + // Current state of the animation. + AnimationState animation_state_; + + // The current index into the Animation image strip. + int animation_frame_; + + DISALLOW_COPY_AND_ASSIGN(LoadingAnimation); + }; + TabRendererGtk(); virtual ~TabRendererGtk(); @@ -35,6 +80,10 @@ class TabRendererGtk { // Returns true if the Tab is selected, false otherwise. virtual bool IsSelected() const; + // Advance the loading animation to the next frame, or hide the animation if + // the tab isn't loading. + void ValidateLoadingAnimation(AnimationState animation_state); + // Returns the minimum possible size of a single unselected Tab. static gfx::Size GetMinimumUnselectedSize(); // Returns the minimum possible size of a selected Tab. Selected tabs must @@ -70,6 +119,28 @@ class TabRendererGtk { std::wstring GetTitle() const; private: + // Model data. We store this here so that we don't need to ask the underlying + // model, which is tricky since instances of this object can outlive the + // corresponding objects in the underlying model. + struct TabData { + SkBitmap favicon; + std::wstring title; + bool loading; + bool crashed; + bool off_the_record; + bool show_icon; + bool show_download_icon; + }; + + // TODO(jhawkins): Move into TabResources class. + struct TabImage { + SkBitmap* image_l; + SkBitmap* image_c; + SkBitmap* image_r; + int l_width; + int r_width; + }; + // Generates the bounds for the interior items of the tab. void Layout(); @@ -103,28 +174,8 @@ class TabRendererGtk { gfx::Rect title_bounds_; gfx::Rect close_button_bounds_; - // Model data. We store this here so that we don't need to ask the underlying - // model, which is tricky since instances of this object can outlive the - // corresponding objects in the underlying model. - struct TabData { - SkBitmap favicon; - std::wstring title; - bool loading; - bool crashed; - bool off_the_record; - bool show_icon; - bool show_download_icon; - }; TabData data_; - // TODO(jhawkins): Move into TabResources class. - struct TabImage { - SkBitmap* image_l; - SkBitmap* image_c; - SkBitmap* image_r; - int l_width; - int r_width; - }; static TabImage tab_active_; static TabImage tab_inactive_; static TabImage tab_inactive_otr_; @@ -170,6 +221,9 @@ class TabRendererGtk { // Set when the mouse is hovering over this tab and the tab is not selected. bool hovering_; + // Contains the loading animation state. + LoadingAnimation loading_animation_; + DISALLOW_COPY_AND_ASSIGN(TabRendererGtk); }; diff --git a/chrome/browser/gtk/tabs/tab_renderer_gtk_unittest.cc b/chrome/browser/gtk/tabs/tab_renderer_gtk_unittest.cc new file mode 100644 index 0000000..0304d26 --- /dev/null +++ b/chrome/browser/gtk/tabs/tab_renderer_gtk_unittest.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/gtk/tabs/tab_renderer_gtk.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +typedef TabRendererGtk::LoadingAnimation LoadingAnimation; + +const int kTestLoadingAnimationFrameCount = 10; +const int kTestWaitingAnimationFrameCount = 40; +const int kTestWaitingToLoadingFrameCountRatio = 4; + +const LoadingAnimation::Data kMockAnimationData = { + NULL, // waiting_animation_frames + NULL, // loading_animation_frames + kTestLoadingAnimationFrameCount, + kTestWaitingAnimationFrameCount, + kTestWaitingToLoadingFrameCountRatio, +}; + +TEST(LoadingAnimationTest, InitialState) { + LoadingAnimation loading_animation(&kMockAnimationData); + EXPECT_EQ(TabRendererGtk::ANIMATION_NONE, + loading_animation.animation_state()); + EXPECT_EQ(0, loading_animation.animation_frame()); +} + +TEST(LoadingAnimationTest, AdvanceWaitingFrames) { + LoadingAnimation loading_animation(&kMockAnimationData); + loading_animation.ValidateLoadingAnimation(TabRendererGtk::ANIMATION_WAITING); + EXPECT_EQ(TabRendererGtk::ANIMATION_WAITING, + loading_animation.animation_state()); + EXPECT_EQ(1, loading_animation.animation_frame()); + loading_animation.ValidateLoadingAnimation(TabRendererGtk::ANIMATION_WAITING); + EXPECT_EQ(TabRendererGtk::ANIMATION_WAITING, + loading_animation.animation_state()); + EXPECT_EQ(2, loading_animation.animation_frame()); + + for (int i = 2; i < kTestWaitingAnimationFrameCount - 1; ++i) { + loading_animation.ValidateLoadingAnimation( + TabRendererGtk::ANIMATION_WAITING); + } + EXPECT_EQ(TabRendererGtk::ANIMATION_WAITING, + loading_animation.animation_state()); + EXPECT_EQ(kTestWaitingAnimationFrameCount - 1, + loading_animation.animation_frame()); + + loading_animation.ValidateLoadingAnimation(TabRendererGtk::ANIMATION_WAITING); + EXPECT_EQ(TabRendererGtk::ANIMATION_WAITING, + loading_animation.animation_state()); + EXPECT_EQ(0, loading_animation.animation_frame()) << + "The animation frame should have wrapped around to 0."; +} + +TEST(LoadingAnimationTest, AdvanceFromWaitingToLoading) { + LoadingAnimation loading_animation(&kMockAnimationData); + for (int i = 0; i < 2 * kTestWaitingToLoadingFrameCountRatio; ++i) { + loading_animation.ValidateLoadingAnimation( + TabRendererGtk::ANIMATION_WAITING); + } + EXPECT_EQ(TabRendererGtk::ANIMATION_WAITING, + loading_animation.animation_state()); + EXPECT_EQ(2 * kTestWaitingToLoadingFrameCountRatio, + loading_animation.animation_frame()); + + loading_animation.ValidateLoadingAnimation(TabRendererGtk::ANIMATION_LOADING); + EXPECT_EQ(TabRendererGtk::ANIMATION_LOADING, + loading_animation.animation_state()); + EXPECT_EQ(kTestLoadingAnimationFrameCount - 1, + loading_animation.animation_frame()); +} + +} // namespace diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/gtk/tabs/tab_strip_gtk.cc index 7e9a153..1c0e5c4 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.cc +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.cc @@ -102,6 +102,26 @@ void TabStripGtk::Layout() { gtk_widget_queue_draw(tabstrip_.get()); } +void TabStripGtk::UpdateLoadingAnimations() { + for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) { + TabGtk* current_tab = GetTabAt(i); + if (current_tab->closing()) { + --index; + } else { + TabContents* contents = model_->GetTabContentsAt(index); + if (!contents || !contents->is_loading()) { + current_tab->ValidateLoadingAnimation(TabGtk::ANIMATION_NONE); + } else if (contents->waiting_for_response()) { + current_tab->ValidateLoadingAnimation(TabGtk::ANIMATION_WAITING); + } else { + current_tab->ValidateLoadingAnimation(TabGtk::ANIMATION_LOADING); + } + } + } + + gtk_widget_queue_draw(tabstrip_.get()); +} + //////////////////////////////////////////////////////////////////////////////// // TabStripGtk, TabStripModelObserver implementation: @@ -294,7 +314,8 @@ int TabStripGtk::GetIndexOfTab(const TabGtk* tab) const { } TabGtk* TabStripGtk::GetTabAt(int index) const { - DCHECK(index >= 0 && index < GetTabCount()); + DCHECK_GE(index, 0); + DCHECK_LT(index, GetTabCount()); return tab_data_.at(index).tab; } diff --git a/chrome/browser/gtk/tabs/tab_strip_gtk.h b/chrome/browser/gtk/tabs/tab_strip_gtk.h index be93e07..6d8f17f 100644 --- a/chrome/browser/gtk/tabs/tab_strip_gtk.h +++ b/chrome/browser/gtk/tabs/tab_strip_gtk.h @@ -8,6 +8,7 @@ #include #include +#include "base/basictypes.h" #include "base/gfx/rect.h" #include "chrome/browser/gtk/tabs/tab_gtk.h" #include "chrome/browser/tabs/tab_strip_model.h" @@ -32,6 +33,9 @@ class TabStripGtk : public TabStripModelObserver, // Sets the bounds of the tabstrip. void SetBounds(const gfx::Rect& bounds) { bounds_ = bounds; } + // Updates loading animations for the TabStrip. + void UpdateLoadingAnimations(); + protected: // TabStripModelObserver implementation: virtual void TabInsertedAt(TabContents* contents, @@ -63,6 +67,11 @@ class TabStripGtk : public TabStripModelObserver, virtual bool HasAvailableDragActions() const; private: + struct TabData { + TabGtk* tab; + gfx::Rect ideal_bounds; + }; + // expose-event handler that redraws the tabstrip static gboolean OnExpose(GtkWidget* widget, GdkEventExpose* e, TabStripGtk* tabstrip); @@ -119,10 +128,6 @@ class TabStripGtk : public TabStripModelObserver, void GenerateIdealBounds(); // The Tabs we contain, and their last generated "good" bounds. - struct TabData { - TabGtk* tab; - gfx::Rect ideal_bounds; - }; std::vector tab_data_; // The current widths of various types of tabs. We save these so that, as @@ -157,6 +162,8 @@ class TabStripGtk : public TabStripModelObserver, // The index of the tab the mouse is currently over. -1 if not over a tab. int hover_index_; + + DISALLOW_COPY_AND_ASSIGN(TabStripGtk); }; #endif // CHROME_BROWSER_GTK_TABS_TAB_STRIP_GTK_H_ diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index f5c7e39..939b49c 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -2094,6 +2094,7 @@ 'browser/extensions/extensions_service_unittest.cc', 'browser/extensions/user_script_master_unittest.cc', 'browser/google_url_tracker_unittest.cc', + 'browser/gtk/tabs/tab_renderer_gtk_unittest.cc', 'browser/history/expire_history_backend_unittest.cc', 'browser/history/history_backend_unittest.cc', 'browser/history/history_querying_unittest.cc', @@ -2232,6 +2233,7 @@ 'sources!': [ 'browser/back_forward_menu_model_unittest.cc', 'browser/download/download_manager_unittest.cc', + 'browser/gtk/tabs/tab_renderer_gtk_unittest.cc', 'browser/navigation_controller_unittest.cc', 'browser/sessions/session_backend_unittest.cc', 'browser/sessions/session_service_test_helper.cc', @@ -2252,6 +2254,7 @@ 'third_party/wtl/include', ], 'sources!': [ + 'browser/gtk/tabs/tab_renderer_gtk_unittest.cc', 'common/file_descriptor_set_unittest.cc', 'common/net/url_util_unittest.cc', ], -- cgit v1.1