diff options
author | tapted <tapted@chromium.org> | 2015-10-22 01:45:07 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-22 08:45:56 +0000 |
commit | 23f218dbb7d9f4fd2401e71bfa5f23af32d13800 (patch) | |
tree | 94ea071881d7277e055a5299c6f008cb9bb68bbf | |
parent | d3f1b8e46a15aa7b04eeb04ddaae723acd9eb469 (diff) | |
download | chromium_src-23f218dbb7d9f4fd2401e71bfa5f23af32d13800.zip chromium_src-23f218dbb7d9f4fd2401e71bfa5f23af32d13800.tar.gz chromium_src-23f218dbb7d9f4fd2401e71bfa5f23af32d13800.tar.bz2 |
Paint tab-loading throbbers into a ui::Layer.
In views browsers, updating the tab loading throbber currently requires
a repaint that starts at the BrowserView and trickles the damage
rectangle down until it reaches the favicon area. This uses a lot of CPU
and energy: about 11 watts above idle on a MacBook Pro (and significant
CPU on Linux) for a single "waiting" spinner.
Giving the throbber a layer allows the repaint to be isolated, with
compositing done on the GPU. This brings total energy usage down to
about 2.5 watts above idle.
Plots at http://crbug.com/391646#c44
BUG=391646
TEST=Spinners should look and behave the same, but use less energy/CPU.
Review URL: https://codereview.chromium.org/1393193002
Cr-Commit-Position: refs/heads/master@{#355514}
-rw-r--r-- | chrome/browser/ui/views/tabs/tab.cc | 155 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/tab.h | 20 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/tab_controller.h | 5 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/tab_strip.cc | 7 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/tab_strip.h | 1 | ||||
-rw-r--r-- | chrome/browser/ui/views/tabs/tab_unittest.cc | 102 |
6 files changed, 224 insertions, 66 deletions
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc index 809d656..a917d51 100644 --- a/chrome/browser/ui/views/tabs/tab.cc +++ b/chrome/browser/ui/views/tabs/tab.cc @@ -342,6 +342,75 @@ class Tab::TabCloseButton : public views::ImageButton, }; //////////////////////////////////////////////////////////////////////////////// +// ThrobberView +// +// A Layer-backed view for updating a waiting or loading tab throbber. +class Tab::ThrobberView : public views::View { + public: + explicit ThrobberView(Tab* owner); + + // Resets the times tracking when the throbber changes state. + void ResetStartTimes(); + + private: + // views::View: + bool CanProcessEventsWithinSubtree() const override; + void OnPaint(gfx::Canvas* canvas) override; + + Tab* owner_; // Weak. Owns |this|. + + // The point in time when the tab icon was first painted in the waiting state. + base::TimeTicks waiting_start_time_; + + // The point in time when the tab icon was first painted in the loading state. + base::TimeTicks loading_start_time_; + + // Paint state for the throbber after the most recent waiting paint. + gfx::ThrobberWaitingState waiting_state_; + + DISALLOW_COPY_AND_ASSIGN(ThrobberView); +}; + +Tab::ThrobberView::ThrobberView(Tab* owner) : owner_(owner) {} + +void Tab::ThrobberView::ResetStartTimes() { + waiting_start_time_ = base::TimeTicks(); + loading_start_time_ = base::TimeTicks(); + waiting_state_ = gfx::ThrobberWaitingState(); +} + +bool Tab::ThrobberView::CanProcessEventsWithinSubtree() const { + return false; +} + +void Tab::ThrobberView::OnPaint(gfx::Canvas* canvas) { + const TabRendererData::NetworkState state = owner_->data().network_state; + if (state == TabRendererData::NETWORK_STATE_NONE) + return; + + ui::ThemeProvider* tp = GetThemeProvider(); + const gfx::Rect bounds = GetLocalBounds(); + if (state == TabRendererData::NETWORK_STATE_WAITING) { + if (waiting_start_time_ == base::TimeTicks()) + waiting_start_time_ = base::TimeTicks::Now(); + + waiting_state_.elapsed_time = base::TimeTicks::Now() - waiting_start_time_; + gfx::PaintThrobberWaiting( + canvas, bounds, tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING), + waiting_state_.elapsed_time); + } else { + if (loading_start_time_ == base::TimeTicks()) + loading_start_time_ = base::TimeTicks::Now(); + + waiting_state_.color = + tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING); + gfx::PaintThrobberSpinningAfterWaiting( + canvas, bounds, tp->GetColor(ThemeProperties::COLOR_THROBBER_SPINNING), + base::TimeTicks::Now() - loading_start_time_, &waiting_state_); + } +} + +//////////////////////////////////////////////////////////////////////////////// // ImageCacheEntry Tab::ImageCacheEntry::ImageCacheEntry() @@ -372,6 +441,7 @@ Tab::Tab(TabController* controller) favicon_hiding_offset_(0), immersive_loading_step_(0), should_display_crashed_favicon_(false), + throbber_(nullptr), media_indicator_button_(nullptr), close_button_(nullptr), title_(new views::Label()), @@ -402,6 +472,10 @@ Tab::Tab(TabController* controller) SetEventTargeter( scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); + throbber_ = new ThrobberView(this); + throbber_->SetVisible(false); + AddChildView(throbber_); + media_indicator_button_ = new MediaIndicatorButton(this); AddChildView(media_indicator_button_); @@ -456,6 +530,7 @@ void Tab::SetData(const TabRendererData& data) { return; TabRendererData old(data_); + UpdateLoadingAnimation(data.network_state); data_ = data; base::string16 title = data_.title; @@ -515,9 +590,8 @@ void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) { return; } - TabRendererData::NetworkState old_state = data_.network_state; data_.network_state = state; - AdvanceLoadingAnimation(old_state, state); + AdvanceLoadingAnimation(); } void Tab::StartPulse() { @@ -797,6 +871,7 @@ void Tab::Layout() { favicon_bounds_.set_y(lb.y() + (lb.height() - gfx::kFaviconSize + 1) / 2); MaybeAdjustLeftForPinnedTab(&favicon_bounds_); } + throbber_->SetBoundsRect(favicon_bounds_); showing_close_button_ = ShouldShowCloseBox(); if (showing_close_button_) { @@ -1353,28 +1428,7 @@ void Tab::PaintIcon(gfx::Canvas* canvas) { return; if (data().network_state != TabRendererData::NETWORK_STATE_NONE) { - // Paint network activity (aka throbber) animation frame. - ui::ThemeProvider* tp = GetThemeProvider(); - if (data().network_state == TabRendererData::NETWORK_STATE_WAITING) { - if (waiting_start_time_ == base::TimeTicks()) - waiting_start_time_ = base::TimeTicks::Now(); - - waiting_state_.elapsed_time = - base::TimeTicks::Now() - waiting_start_time_; - gfx::PaintThrobberWaiting( - canvas, bounds, tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING), - waiting_state_.elapsed_time); - } else { - if (loading_start_time_ == base::TimeTicks()) - loading_start_time_ = base::TimeTicks::Now(); - - waiting_state_.color = - tp->GetColor(ThemeProperties::COLOR_THROBBER_WAITING); - gfx::PaintThrobberSpinningAfterWaiting( - canvas, bounds, - tp->GetColor(ThemeProperties::COLOR_THROBBER_SPINNING), - base::TimeTicks::Now() - loading_start_time_, &waiting_state_); - } + // Throbber will do its own painting. } else { const gfx::ImageSkia& favicon = should_display_crashed_favicon_ ? *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( @@ -1388,27 +1442,46 @@ void Tab::PaintIcon(gfx::Canvas* canvas) { } } -void Tab::AdvanceLoadingAnimation(TabRendererData::NetworkState old_state, - TabRendererData::NetworkState state) { - if (state == TabRendererData::NETWORK_STATE_WAITING) { - // Waiting steps backwards. - immersive_loading_step_ = - (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) % - kImmersiveLoadingStepCount; - } else if (state == TabRendererData::NETWORK_STATE_LOADING) { - immersive_loading_step_ = (immersive_loading_step_ + 1) % - kImmersiveLoadingStepCount; - } else { - waiting_start_time_ = base::TimeTicks(); - loading_start_time_ = base::TimeTicks(); - waiting_state_ = gfx::ThrobberWaitingState(); - immersive_loading_step_ = 0; - } +void Tab::AdvanceLoadingAnimation() { + const TabRendererData::NetworkState state = data().network_state; if (controller_->IsImmersiveStyle()) { + if (state == TabRendererData::NETWORK_STATE_WAITING) { + // Waiting steps backwards. + immersive_loading_step_ = + (immersive_loading_step_ - 1 + kImmersiveLoadingStepCount) % + kImmersiveLoadingStepCount; + } else if (state == TabRendererData::NETWORK_STATE_LOADING) { + immersive_loading_step_ = + (immersive_loading_step_ + 1) % kImmersiveLoadingStepCount; + } else { + immersive_loading_step_ = 0; + } + SchedulePaintInRect(GetImmersiveBarRect()); - } else { + return; + } + + if (state == TabRendererData::NETWORK_STATE_NONE) { + throbber_->ResetStartTimes(); + throbber_->SetVisible(false); ScheduleIconPaint(); + return; + } + + // Since the throbber can animate for a long time, paint to a separate layer + // when possible to reduce repaint overhead. + const bool paint_to_layer = controller_->CanPaintThrobberToLayer(); + if (paint_to_layer != !!throbber_->layer()) { + throbber_->SetPaintToLayer(paint_to_layer); + throbber_->SetFillsBoundsOpaquely(false); + if (paint_to_layer) + ScheduleIconPaint(); // Ensure the non-layered throbber goes away. + } + if (!throbber_->visible()) { + ScheduleIconPaint(); // Repaint the icon area to hide the favicon. + throbber_->SetVisible(true); } + throbber_->SchedulePaint(); } int Tab::IconCapacity() const { diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h index 8ecd591..7034991 100644 --- a/chrome/browser/ui/views/tabs/tab.h +++ b/chrome/browser/ui/views/tabs/tab.h @@ -89,8 +89,7 @@ class Tab : public gfx::AnimationDelegate, void SetData(const TabRendererData& data); const TabRendererData& data() const { return data_; } - // Sets the network state. If the network state changes NetworkStateChanged is - // invoked. + // Sets the network state. void UpdateLoadingAnimation(TabRendererData::NetworkState state); // Starts/Stops a pulse animation. @@ -156,15 +155,15 @@ class Tab : public gfx::AnimationDelegate, private: friend class TabTest; - FRIEND_TEST_ALL_PREFIXES(TabTest, CloseButtonLayout); - friend class TabStripTest; FRIEND_TEST_ALL_PREFIXES(TabStripTest, TabHitTestMaskWhenStacked); FRIEND_TEST_ALL_PREFIXES(TabStripTest, TabCloseButtonVisibilityWhenStacked); // The animation object used to swap the favicon with the sad tab icon. class FaviconCrashAnimation; + class TabCloseButton; + class ThrobberView; // Contains a cached image and the values used to generate it. struct ImageCacheEntry { @@ -247,8 +246,7 @@ class Tab : public gfx::AnimationDelegate, void PaintIcon(gfx::Canvas* canvas); // Invoked if data_.network_state changes, or the network_state is not none. - void AdvanceLoadingAnimation(TabRendererData::NetworkState old_state, - TabRendererData::NetworkState state); + void AdvanceLoadingAnimation(); // Returns the number of favicon-size elements that can fit in the tab's // current size. @@ -331,15 +329,6 @@ class Tab : public gfx::AnimationDelegate, // crashes. int favicon_hiding_offset_; - // The point in time when the tab icon was first painted in the waiting state. - base::TimeTicks waiting_start_time_; - - // The point in time when the tab icon was first painted in the loading state. - base::TimeTicks loading_start_time_; - - // Paint state for the throbber after the most recent waiting paint. - gfx::ThrobberWaitingState waiting_state_; - // Step in the immersive loading progress indicator. int immersive_loading_step_; @@ -355,6 +344,7 @@ class Tab : public gfx::AnimationDelegate, scoped_refptr<gfx::AnimationContainer> animation_container_; + ThrobberView* throbber_; MediaIndicatorButton* media_indicator_button_; views::ImageButton* close_button_; views::Label* title_; diff --git a/chrome/browser/ui/views/tabs/tab_controller.h b/chrome/browser/ui/views/tabs/tab_controller.h index 3e8807b..feced12 100644 --- a/chrome/browser/ui/views/tabs/tab_controller.h +++ b/chrome/browser/ui/views/tabs/tab_controller.h @@ -93,6 +93,11 @@ class TabController { // set to the clip (if |clip| is empty means no clip). virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) = 0; + // Returns true if tab loading throbbers can be painted to a composited layer. + // This can only be done when the TabController can guarantee that nothing + // in the same window will redraw on top of the the favicon area of any tab. + virtual bool CanPaintThrobberToLayer() const = 0; + // Returns true if tabs painted in the rectangular light-bar style. virtual bool IsImmersiveStyle() const = 0; diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc index 5269e99..dc05ac7 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.cc +++ b/chrome/browser/ui/views/tabs/tab_strip.cc @@ -1220,6 +1220,13 @@ bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) { return true; } +bool TabStrip::CanPaintThrobberToLayer() const { + // Disable layer-painting of throbbers if dragging, if any tab animation is in + // progress, or if stacked tabs are enabled. + const bool dragging = drag_controller_ && drag_controller_->started_drag(); + return !touch_layout_ && !dragging && !IsAnimating(); +} + bool TabStrip::IsImmersiveStyle() const { return immersive_style_; } diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h index f682871..b8c9b18 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.h +++ b/chrome/browser/ui/views/tabs/tab_strip.h @@ -224,6 +224,7 @@ class TabStrip : public views::View, void OnMouseEventInTab(views::View* source, const ui::MouseEvent& event) override; bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) override; + bool CanPaintThrobberToLayer() const override; bool IsImmersiveStyle() const override; void UpdateTabAccessibilityState(const Tab* tab, ui::AXViewState* state) override; diff --git a/chrome/browser/ui/views/tabs/tab_unittest.cc b/chrome/browser/ui/views/tabs/tab_unittest.cc index 5c67ac2..c0c0e86 100644 --- a/chrome/browser/ui/views/tabs/tab_unittest.cc +++ b/chrome/browser/ui/views/tabs/tab_unittest.cc @@ -20,12 +20,13 @@ using views::Widget; class FakeTabController : public TabController { public: - FakeTabController() : immersive_style_(false), active_tab_(false) { - } - ~FakeTabController() override {} + FakeTabController() {} void set_immersive_style(bool value) { immersive_style_ = value; } void set_active_tab(bool value) { active_tab_ = value; } + void set_paint_throbber_to_layer(bool value) { + paint_throbber_to_layer_ = value; + } const ui::ListSelectionModel& GetSelectionModel() override { return selection_model_; @@ -59,14 +60,18 @@ class FakeTabController : public TabController { void OnMouseEventInTab(views::View* source, const ui::MouseEvent& event) override {} bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) override { return true; } + bool CanPaintThrobberToLayer() const override { + return paint_throbber_to_layer_; + } bool IsImmersiveStyle() const override { return immersive_style_; } void UpdateTabAccessibilityState(const Tab* tab, ui::AXViewState* state) override{}; private: ui::ListSelectionModel selection_model_; - bool immersive_style_; - bool active_tab_; + bool immersive_style_ = false; + bool active_tab_ = false; + bool paint_throbber_to_layer_ = true; DISALLOW_COPY_AND_ASSIGN(FakeTabController); }; @@ -93,6 +98,22 @@ class TabTest : public views::ViewsTestBase, base::i18n::SetICUDefaultLocale(original_locale_); } + static views::ImageButton* GetCloseButton(const Tab& tab) { + return tab.close_button_; + } + + static views::View* GetThrobberView(const Tab& tab) { + // Reinterpret to keep the definition encapsulated (which works so long as + // multiple inheritance isn't involved). + return reinterpret_cast<views::View*>(tab.throbber_); + } + + static gfx::Rect GetFaviconBounds(const Tab& tab) { + return tab.favicon_bounds_; + } + + static void LayoutTab(Tab* tab) { tab->Layout(); } + static void CheckForExpectedLayoutAndVisibilityOfElements(const Tab& tab) { // Check whether elements are visible when they are supposed to be, given // Tab size and TabRendererData state. @@ -393,17 +414,78 @@ TEST_P(TabTest, CloseButtonLayout) { FakeTabController tab_controller; Tab tab(&tab_controller); tab.SetBounds(0, 0, 100, 50); - tab.Layout(); - gfx::Insets close_button_insets = tab.close_button_->GetInsets(); - tab.Layout(); - gfx::Insets close_button_insets_2 = tab.close_button_->GetInsets(); + LayoutTab(&tab); + gfx::Insets close_button_insets = GetCloseButton(tab)->GetInsets(); + LayoutTab(&tab); + gfx::Insets close_button_insets_2 = GetCloseButton(tab)->GetInsets(); EXPECT_EQ(close_button_insets.top(), close_button_insets_2.top()); EXPECT_EQ(close_button_insets.left(), close_button_insets_2.left()); EXPECT_EQ(close_button_insets.bottom(), close_button_insets_2.bottom()); EXPECT_EQ(close_button_insets.right(), close_button_insets_2.right()); // Also make sure the close button is sized as large as the tab. - EXPECT_EQ(50, tab.close_button_->bounds().height()); + EXPECT_EQ(50, GetCloseButton(tab)->bounds().height()); +} + +// Tests expected changes to the ThrobberView state when the WebContents loading +// state changes or the animation timer (usually in BrowserView) triggers. +TEST_P(TabTest, LayeredThrobber) { + if (testing_for_rtl_locale() && !base::i18n::IsRTL()) { + LOG(WARNING) << "Testing of RTL locale not supported on current platform."; + return; + } + + Widget widget; + InitWidget(&widget); + + FakeTabController tab_controller; + Tab tab(&tab_controller); + widget.GetContentsView()->AddChildView(&tab); + tab.SetBoundsRect(gfx::Rect(Tab::GetStandardSize())); + + views::View* throbber = GetThrobberView(tab); + EXPECT_FALSE(throbber->visible()); + EXPECT_EQ(TabRendererData::NETWORK_STATE_NONE, tab.data().network_state); + EXPECT_EQ(throbber->bounds(), GetFaviconBounds(tab)); + + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE); + EXPECT_FALSE(throbber->visible()); + + // Simulate a "normal" tab load: should paint to a layer. + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING); + EXPECT_TRUE(tab_controller.CanPaintThrobberToLayer()); + EXPECT_TRUE(throbber->visible()); + EXPECT_TRUE(throbber->layer()); + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_LOADING); + EXPECT_TRUE(throbber->visible()); + EXPECT_TRUE(throbber->layer()); + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE); + EXPECT_FALSE(throbber->visible()); + + // Simulate a drag started and stopped during a load: layer painting stops + // temporarily. + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING); + EXPECT_TRUE(throbber->visible()); + EXPECT_TRUE(throbber->layer()); + tab_controller.set_paint_throbber_to_layer(false); + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING); + EXPECT_TRUE(throbber->visible()); + EXPECT_FALSE(throbber->layer()); + tab_controller.set_paint_throbber_to_layer(true); + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING); + EXPECT_TRUE(throbber->visible()); + EXPECT_TRUE(throbber->layer()); + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE); + EXPECT_FALSE(throbber->visible()); + + // Simulate a tab load starting and stopping during tab dragging (or with + // stacked tabs): no layer painting. + tab_controller.set_paint_throbber_to_layer(false); + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING); + EXPECT_TRUE(throbber->visible()); + EXPECT_FALSE(throbber->layer()); + tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE); + EXPECT_FALSE(throbber->visible()); } // Test in both a LTR and a RTL locale. Note: The fact that the UI code is |