Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(340)

Side by Side Diff: chrome/browser/tab_contents/thumbnail_generator.cc

Issue 118420: Adds kind-of-live thumbnail generation for a potential tab switcher. This... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2009 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/tab_contents/thumbnail_generator.h"
6
7 #include <algorithm>
8
9 #include "base/histogram.h"
10 #include "base/time.h"
11 #include "chrome/browser/renderer_host/backing_store.h"
12 #include "chrome/browser/renderer_host/render_view_host.h"
13 #include "chrome/common/notification_service.h"
14 #include "chrome/common/property_bag.h"
15 #include "skia/ext/image_operations.h"
16 #include "skia/ext/platform_canvas.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18
19 // Overview
20 // --------
21 // This class provides current thumbnails for tabs. The simplest operation is
22 // when a request for a thumbnail comes in, to grab the backing store and make
23 // a smaller version of that.
24 //
25 // A complication happens because we don't always have nice backing stores for
26 // all tabs (there is a cache of several tabs we'll keep backing stores for).
27 // To get thumbnails for tabs with expired backing stores, we listen for
28 // backing stores that are being thrown out, and generate thumbnails before
29 // that happens. We attach them to the RenderWidgetHost via the property bag
30 // so we can retrieve them later. When a tab has a live backing store again,
31 // we throw away the thumbnail since it's now out-of-date.
32 //
33 // Another complication is performance. If the user brings up a tab switcher, we
34 // don't want to get all 5 cached backing stores since it is a very large amount
35 // of data. As a result, we generate thumbnails for tabs that are hidden even
36 // if the backing store is still valid. This means we'll have to do a maximum
37 // of generating thumbnails for the visible tabs at any point.
38 //
39 // The last performance consideration is when the user switches tabs quickly.
40 // This can happen by doing Control-PageUp/Down or juct clicking quickly on
41 // many different tabs (like when you're looking for one). We don't want to
42 // slow this down by making thumbnails for each tab as it's hidden. Therefore,
43 // we have a timer so that we don't invalidate thumbnails for tabs that are
44 // only shown briefly (which would cause the thumbnail to be regenerated when
45 // the tab is hidden).
46
47 namespace {
48
49 static const int kThumbnailWidth = 294;
50 static const int kThumbnailHeight = 204;
51
52 // Indicates the time that the RWH must be visible for us to update the
53 // thumbnail on it. If the user holds down control enter, there will be a lot
54 // of backing stores created and destroyed. WE don't want to interfere with
55 // that.
56 //
57 // Any operation that happens within this time of being shown is ignored.
58 // This means we won't throw the thumbnail away when the backing store is
59 // painted in this time.
60 static const int kVisibilitySlopMS = 3000;
61
62 struct WidgetThumbnail {
63 SkBitmap thumbnail;
64
65 // Indicates the last time the RWH was shown and hidden.
66 base::TimeTicks last_shown;
67 base::TimeTicks last_hidden;
68 };
69
70 PropertyAccessor<WidgetThumbnail>* GetThumbnailAccessor() {
71 static PropertyAccessor<WidgetThumbnail> accessor;
72 return &accessor;
73 }
74
75 // Returns the existing WidgetThumbnail for a RVH, or creates a new one and
76 // returns that if none exists.
77 WidgetThumbnail* GetDataForHost(RenderWidgetHost* host) {
78 WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
79 host->property_bag());
80 if (wt)
81 return wt;
82
83 GetThumbnailAccessor()->SetProperty(host->property_bag(),
84 WidgetThumbnail());
85 return GetThumbnailAccessor()->GetProperty(host->property_bag());
86 }
87
88 // Creates a downsampled thumbnail for the given backing store. The returned
89 // bitmap will be isNull if there was an error creating it.
90 SkBitmap GetThumbnailForBackingStore(BackingStore* backing_store) {
91 SkBitmap result;
92
93 #if defined(OS_WIN)
94 // Get the bitmap as a Skia object so we can resample it. This is a large
95 // allocation and we can tolerate failure here, so give up if the allocation
96 // fails.
97 base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now();
98
99 skia::PlatformCanvas temp_canvas;
100 if (!temp_canvas.initialize(backing_store->size().width(),
101 backing_store->size().height(), true))
102 return SkBitmap();
103 HDC temp_dc = temp_canvas.beginPlatformPaint();
104 BitBlt(temp_dc,
105 0, 0, backing_store->size().width(), backing_store->size().height(),
106 backing_store->hdc(), 0, 0, SRCCOPY);
107 temp_canvas.endPlatformPaint();
108
109 // Get the bitmap out of the canvas and resample it.
110 const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false);
111 result = skia::ImageOperations::DownsampleByTwoUntilSize(
112 bmp,
113 kThumbnailWidth, kThumbnailHeight);
114 if (bmp.width() == result.width() && bmp.height() == result.height()) {
115 // This is a bit subtle. SkBitmaps are refcounted, but the magic ones in
116 // PlatformCanvas can't be ssigned to SkBitmap with proper refcounting.
117 // If the bitmap doesn't change, then the downsampler will return the input
118 // bitmap, which will be the reference to the weird PlatformCanvas one
119 // insetad of a regular one. To get a regular refcounted bitmap, we need to
120 // copy it.
121 bmp.copyTo(&result, SkBitmap::kARGB_8888_Config);
122 }
123
124 HISTOGRAM_TIMES("Thumbnail.ComputeOnDestroyMS",
125 base::TimeTicks::Now() - begin_compute_thumbnail);
126 #endif
127
128 return result;
129 }
130
131 } // namespace
132
133 ThumbnailGenerator::ThumbnailGenerator()
134 : no_timeout_(false) {
135 // Even though we deal in RenderWidgetHosts, we only care about its subclass,
136 // RenderViewHost when it is in a tab. We don't make thumbnails for
137 // RenderViewHosts that aren't in tabs, or RenderWidgetHosts that aren't
138 // views like select popups.
139 registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
140 NotificationService::AllSources());
141
142 registrar_.Add(this, NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED,
143 NotificationService::AllSources());
144 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
145 NotificationService::AllSources());
146 }
147
148 ThumbnailGenerator::~ThumbnailGenerator() {
149 }
150
151 SkBitmap ThumbnailGenerator::GetThumbnailForRenderer(
152 RenderWidgetHost* renderer) const {
153 // Return a cached one if we have it and it's still valid. This will only be
154 // valid when there used to be a backing store, but there isn't now.
155 WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
156 renderer->property_bag());
157 if (wt && !wt->thumbnail.isNull() &&
158 (no_timeout_ ||
159 base::TimeTicks::Now() -
160 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown))
161 return wt->thumbnail;
162
163 BackingStore* backing_store = renderer->GetBackingStore(false);
164 if (!backing_store)
165 return SkBitmap();
166
167 // Save this thumbnail in case we need to use it again soon. It will be
168 // invalidated on the next paint.
169 wt->thumbnail = GetThumbnailForBackingStore(backing_store);
170 return wt->thumbnail;
171 }
172
173 void ThumbnailGenerator::WidgetWillDestroyBackingStore(
174 RenderWidgetHost* widget,
175 BackingStore* backing_store) {
176 // Since the backing store is going away, we need to save it as a thumbnail.
177 WidgetThumbnail* wt = GetDataForHost(widget);
178
179 // If there is already a thumbnail on the RWH that's visible, it means that
180 // not enough time has elapsed since being shown, and we can ignore generating
181 // a new one.
182 if (!wt->thumbnail.isNull())
183 return;
184
185 // Save a scaled-down image of the page in case we're asked for the thumbnail
186 // when there is no RenderViewHost. If this fails, we don't want to overwrite
187 // an existing thumbnail.
188 SkBitmap new_thumbnail = GetThumbnailForBackingStore(backing_store);
189 if (!new_thumbnail.isNull())
190 wt->thumbnail = new_thumbnail;
191 }
192
193 void ThumbnailGenerator::WidgetDidUpdateBackingStore(
194 RenderWidgetHost* widget) {
195 // Clear the current thumbnail since it's no longer valid.
196 WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
197 widget->property_bag());
198 if (!wt)
199 return; // Nothing to do.
200
201 // If this operation is within the time slop after being shown, keep the
202 // existing thumbnail.
203 if (no_timeout_ ||
204 base::TimeTicks::Now() -
205 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown)
206 return; // TODO(brettw) schedule thumbnail generation for this renderer in
207 // case we don't get a paint for it after the time slop, but it's
208 // still visible.
209
210 // Clear the thumbnail, since it's now out of date.
211 wt->thumbnail = SkBitmap();
212 }
213
214 void ThumbnailGenerator::Observe(NotificationType type,
215 const NotificationSource& source,
216 const NotificationDetails& details) {
217 switch (type.value) {
218 case NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB: {
219 // Install our observer for all new RVHs.
220 RenderViewHost* renderer = Details<RenderViewHost>(details).ptr();
221 renderer->set_painting_observer(this);
222 break;
223 }
224
225 case NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED:
226 if (*Details<bool>(details).ptr())
227 WidgetShown(Source<RenderWidgetHost>(source).ptr());
228 else
229 WidgetHidden(Source<RenderWidgetHost>(source).ptr());
230 break;
231
232 case NotificationType::RENDER_WIDGET_HOST_DESTROYED:
233 WidgetDestroyed(Source<RenderWidgetHost>(source).ptr());
234 break;
235
236 default:
237 NOTREACHED();
238 }
239 }
240
241 void ThumbnailGenerator::WidgetShown(RenderWidgetHost* widget) {
242 WidgetThumbnail* wt = GetDataForHost(widget);
243 wt->last_shown = base::TimeTicks::Now();
244
245 // If there is no thumbnail (like we're displaying a background tab for the
246 // first time), then we don't have do to invalidate the existing one.
247 if (wt->thumbnail.isNull())
248 return;
249
250 std::vector<RenderWidgetHost*>::iterator found =
251 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget);
252 if (found != shown_hosts_.end()) {
253 NOTREACHED() << "Showing a RWH we already think is shown";
254 shown_hosts_.erase(found);
255 }
256 shown_hosts_.push_back(widget);
257
258 // Keep the old thumbnail for a small amount of time after the tab has been
259 // shown. This is so in case it's hidden quickly again, we don't waste any
260 // work regenerating it.
261 if (timer_.IsRunning())
262 return;
263 timer_.Start(base::TimeDelta::FromMilliseconds(
264 no_timeout_ ? 0 : kVisibilitySlopMS),
265 this, &ThumbnailGenerator::ShownDelayHandler);
266 }
267
268 void ThumbnailGenerator::WidgetHidden(RenderWidgetHost* widget) {
269 WidgetThumbnail* wt = GetDataForHost(widget);
270 wt->last_hidden = base::TimeTicks::Now();
271
272 // If the tab is on the list of ones to invalidate the thumbnail, we need to
273 // remove it.
274 EraseHostFromShownList(widget);
275
276 // There may still be a valid cached thumbnail on the RWH, so we don't need to
277 // make a new one.
278 if (!wt->thumbnail.isNull())
279 return;
280 wt->thumbnail = GetThumbnailForRenderer(widget);
281 }
282
283 void ThumbnailGenerator::WidgetDestroyed(RenderWidgetHost* widget) {
284 EraseHostFromShownList(widget);
285 }
286
287 void ThumbnailGenerator::ShownDelayHandler() {
288 base::TimeTicks threshold = base::TimeTicks::Now() -
289 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS);
290
291 // Check the list of all pending RWHs (normally only one) to see if any of
292 // their times have expired.
293 for (size_t i = 0; i < shown_hosts_.size(); i++) {
294 WidgetThumbnail* wt = GetDataForHost(shown_hosts_[i]);
295 if (no_timeout_ || wt->last_shown <= threshold) {
296 // This thumbnail has expired, delete it.
297 wt->thumbnail = SkBitmap();
298 shown_hosts_.erase(shown_hosts_.begin() + i);
299 i--;
300 }
301 }
302
303 // We need to schedule another run if there are still items in the list to
304 // process. We use half the timeout for these re-runs to catch the items
305 // that were added since the timer was run the first time.
306 if (!shown_hosts_.empty()) {
307 DCHECK(!no_timeout_);
308 timer_.Start(base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) / 2, this,
309 &ThumbnailGenerator::ShownDelayHandler);
310 }
311 }
312
313 void ThumbnailGenerator::EraseHostFromShownList(RenderWidgetHost* widget) {
314 std::vector<RenderWidgetHost*>::iterator found =
315 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget);
316 if (found != shown_hosts_.end())
317 shown_hosts_.erase(found);
318 }
OLDNEW
« no previous file with comments | « chrome/browser/tab_contents/thumbnail_generator.h ('k') | chrome/browser/tab_contents/thumbnail_generator_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698