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

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

Issue 126101: Adds kind-of-live thumbnail generation for a potential tab switcher. (Closed)
Patch Set: For Checking hopefully 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
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 // TODO(brettw) write this for other platforms. If you enable this, be sure
94 // to also enable the unit tests for the same platform in
95 // thumbnail_generator_unittest.cc
96 #if defined(OS_WIN)
97 // Get the bitmap as a Skia object so we can resample it. This is a large
98 // allocation and we can tolerate failure here, so give up if the allocation
99 // fails.
100 base::TimeTicks begin_compute_thumbnail = base::TimeTicks::Now();
101
102 skia::PlatformCanvas temp_canvas;
103 if (!temp_canvas.initialize(backing_store->size().width(),
104 backing_store->size().height(), true))
105 return SkBitmap();
106 HDC temp_dc = temp_canvas.beginPlatformPaint();
107 BitBlt(temp_dc,
108 0, 0, backing_store->size().width(), backing_store->size().height(),
109 backing_store->hdc(), 0, 0, SRCCOPY);
110 temp_canvas.endPlatformPaint();
111
112 // Get the bitmap out of the canvas and resample it.
113 const SkBitmap& bmp = temp_canvas.getTopPlatformDevice().accessBitmap(false);
114 result = skia::ImageOperations::DownsampleByTwoUntilSize(
115 bmp,
116 kThumbnailWidth, kThumbnailHeight);
117 if (bmp.width() == result.width() && bmp.height() == result.height()) {
118 // This is a bit subtle. SkBitmaps are refcounted, but the magic ones in
119 // PlatformCanvas can't be ssigned to SkBitmap with proper refcounting.
120 // If the bitmap doesn't change, then the downsampler will return the input
121 // bitmap, which will be the reference to the weird PlatformCanvas one
122 // insetad of a regular one. To get a regular refcounted bitmap, we need to
123 // copy it.
124 bmp.copyTo(&result, SkBitmap::kARGB_8888_Config);
125 }
126
127 HISTOGRAM_TIMES("Thumbnail.ComputeOnDestroyMS",
128 base::TimeTicks::Now() - begin_compute_thumbnail);
129 #endif
130
131 return result;
132 }
133
134 } // namespace
135
136 ThumbnailGenerator::ThumbnailGenerator()
137 : no_timeout_(false) {
138 // Even though we deal in RenderWidgetHosts, we only care about its subclass,
139 // RenderViewHost when it is in a tab. We don't make thumbnails for
140 // RenderViewHosts that aren't in tabs, or RenderWidgetHosts that aren't
141 // views like select popups.
142 registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
143 NotificationService::AllSources());
144
145 registrar_.Add(this, NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED,
146 NotificationService::AllSources());
147 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED,
148 NotificationService::AllSources());
149 }
150
151 ThumbnailGenerator::~ThumbnailGenerator() {
152 }
153
154 SkBitmap ThumbnailGenerator::GetThumbnailForRenderer(
155 RenderWidgetHost* renderer) const {
156 // Return a cached one if we have it and it's still valid. This will only be
157 // valid when there used to be a backing store, but there isn't now.
158 WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
159 renderer->property_bag());
160 if (wt && !wt->thumbnail.isNull() &&
161 (no_timeout_ ||
162 base::TimeTicks::Now() -
163 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown))
164 return wt->thumbnail;
165
166 BackingStore* backing_store = renderer->GetBackingStore(false);
167 if (!backing_store)
168 return SkBitmap();
169
170 // Save this thumbnail in case we need to use it again soon. It will be
171 // invalidated on the next paint.
172 wt->thumbnail = GetThumbnailForBackingStore(backing_store);
173 return wt->thumbnail;
174 }
175
176 void ThumbnailGenerator::WidgetWillDestroyBackingStore(
177 RenderWidgetHost* widget,
178 BackingStore* backing_store) {
179 // Since the backing store is going away, we need to save it as a thumbnail.
180 WidgetThumbnail* wt = GetDataForHost(widget);
181
182 // If there is already a thumbnail on the RWH that's visible, it means that
183 // not enough time has elapsed since being shown, and we can ignore generating
184 // a new one.
185 if (!wt->thumbnail.isNull())
186 return;
187
188 // Save a scaled-down image of the page in case we're asked for the thumbnail
189 // when there is no RenderViewHost. If this fails, we don't want to overwrite
190 // an existing thumbnail.
191 SkBitmap new_thumbnail = GetThumbnailForBackingStore(backing_store);
192 if (!new_thumbnail.isNull())
193 wt->thumbnail = new_thumbnail;
194 }
195
196 void ThumbnailGenerator::WidgetDidUpdateBackingStore(
197 RenderWidgetHost* widget) {
198 // Clear the current thumbnail since it's no longer valid.
199 WidgetThumbnail* wt = GetThumbnailAccessor()->GetProperty(
200 widget->property_bag());
201 if (!wt)
202 return; // Nothing to do.
203
204 // If this operation is within the time slop after being shown, keep the
205 // existing thumbnail.
206 if (no_timeout_ ||
207 base::TimeTicks::Now() -
208 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) < wt->last_shown)
209 return; // TODO(brettw) schedule thumbnail generation for this renderer in
210 // case we don't get a paint for it after the time slop, but it's
211 // still visible.
212
213 // Clear the thumbnail, since it's now out of date.
214 wt->thumbnail = SkBitmap();
215 }
216
217 void ThumbnailGenerator::Observe(NotificationType type,
218 const NotificationSource& source,
219 const NotificationDetails& details) {
220 switch (type.value) {
221 case NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB: {
222 // Install our observer for all new RVHs.
223 RenderViewHost* renderer = Details<RenderViewHost>(details).ptr();
224 renderer->set_painting_observer(this);
225 break;
226 }
227
228 case NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED:
229 if (*Details<bool>(details).ptr())
230 WidgetShown(Source<RenderWidgetHost>(source).ptr());
231 else
232 WidgetHidden(Source<RenderWidgetHost>(source).ptr());
233 break;
234
235 case NotificationType::RENDER_WIDGET_HOST_DESTROYED:
236 WidgetDestroyed(Source<RenderWidgetHost>(source).ptr());
237 break;
238
239 default:
240 NOTREACHED();
241 }
242 }
243
244 void ThumbnailGenerator::WidgetShown(RenderWidgetHost* widget) {
245 WidgetThumbnail* wt = GetDataForHost(widget);
246 wt->last_shown = base::TimeTicks::Now();
247
248 // If there is no thumbnail (like we're displaying a background tab for the
249 // first time), then we don't have do to invalidate the existing one.
250 if (wt->thumbnail.isNull())
251 return;
252
253 std::vector<RenderWidgetHost*>::iterator found =
254 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget);
255 if (found != shown_hosts_.end()) {
256 NOTREACHED() << "Showing a RWH we already think is shown";
257 shown_hosts_.erase(found);
258 }
259 shown_hosts_.push_back(widget);
260
261 // Keep the old thumbnail for a small amount of time after the tab has been
262 // shown. This is so in case it's hidden quickly again, we don't waste any
263 // work regenerating it.
264 if (timer_.IsRunning())
265 return;
266 timer_.Start(base::TimeDelta::FromMilliseconds(
267 no_timeout_ ? 0 : kVisibilitySlopMS),
268 this, &ThumbnailGenerator::ShownDelayHandler);
269 }
270
271 void ThumbnailGenerator::WidgetHidden(RenderWidgetHost* widget) {
272 WidgetThumbnail* wt = GetDataForHost(widget);
273 wt->last_hidden = base::TimeTicks::Now();
274
275 // If the tab is on the list of ones to invalidate the thumbnail, we need to
276 // remove it.
277 EraseHostFromShownList(widget);
278
279 // There may still be a valid cached thumbnail on the RWH, so we don't need to
280 // make a new one.
281 if (!wt->thumbnail.isNull())
282 return;
283 wt->thumbnail = GetThumbnailForRenderer(widget);
284 }
285
286 void ThumbnailGenerator::WidgetDestroyed(RenderWidgetHost* widget) {
287 EraseHostFromShownList(widget);
288 }
289
290 void ThumbnailGenerator::ShownDelayHandler() {
291 base::TimeTicks threshold = base::TimeTicks::Now() -
292 base::TimeDelta::FromMilliseconds(kVisibilitySlopMS);
293
294 // Check the list of all pending RWHs (normally only one) to see if any of
295 // their times have expired.
296 for (size_t i = 0; i < shown_hosts_.size(); i++) {
297 WidgetThumbnail* wt = GetDataForHost(shown_hosts_[i]);
298 if (no_timeout_ || wt->last_shown <= threshold) {
299 // This thumbnail has expired, delete it.
300 wt->thumbnail = SkBitmap();
301 shown_hosts_.erase(shown_hosts_.begin() + i);
302 i--;
303 }
304 }
305
306 // We need to schedule another run if there are still items in the list to
307 // process. We use half the timeout for these re-runs to catch the items
308 // that were added since the timer was run the first time.
309 if (!shown_hosts_.empty()) {
310 DCHECK(!no_timeout_);
311 timer_.Start(base::TimeDelta::FromMilliseconds(kVisibilitySlopMS) / 2, this,
312 &ThumbnailGenerator::ShownDelayHandler);
313 }
314 }
315
316 void ThumbnailGenerator::EraseHostFromShownList(RenderWidgetHost* widget) {
317 std::vector<RenderWidgetHost*>::iterator found =
318 std::find(shown_hosts_.begin(), shown_hosts_.end(), widget);
319 if (found != shown_hosts_.end())
320 shown_hosts_.erase(found);
321 }
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