summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortapted <tapted@chromium.org>2015-10-22 01:45:07 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-22 08:45:56 +0000
commit23f218dbb7d9f4fd2401e71bfa5f23af32d13800 (patch)
tree94ea071881d7277e055a5299c6f008cb9bb68bbf
parentd3f1b8e46a15aa7b04eeb04ddaae723acd9eb469 (diff)
downloadchromium_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.cc155
-rw-r--r--chrome/browser/ui/views/tabs/tab.h20
-rw-r--r--chrome/browser/ui/views/tabs/tab_controller.h5
-rw-r--r--chrome/browser/ui/views/tabs/tab_strip.cc7
-rw-r--r--chrome/browser/ui/views/tabs/tab_strip.h1
-rw-r--r--chrome/browser/ui/views/tabs/tab_unittest.cc102
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