1 // Copyright 2013 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.
5 #include "content/browser/frame_host/navigation_entry_screenshot_manager.h"
7 #include "base/command_line.h"
8 #include "base/threading/worker_pool.h"
9 #include "content/browser/frame_host/navigation_controller_impl.h"
10 #include "content/browser/frame_host/navigation_entry_impl.h"
11 #include "content/browser/renderer_host/render_view_host_impl.h"
12 #include "content/public/browser/render_widget_host.h"
13 #include "content/public/browser/render_widget_host_view.h"
14 #include "content/public/common/content_switches.h"
15 #include "third_party/skia/include/core/SkCanvas.h"
16 #include "third_party/skia/include/core/SkPaint.h"
17 #include "third_party/skia/include/effects/SkLumaColorFilter.h"
18 #include "ui/gfx/codec/png_codec.h"
22 // Minimum delay between taking screenshots.
23 const int kMinScreenshotIntervalMS = 1000;
29 // Converts SkBitmap to grayscale and encodes to PNG data in a worker thread.
30 class ScreenshotData : public base::RefCountedThreadSafe<ScreenshotData> {
35 void EncodeScreenshot(const SkBitmap& bitmap, base::Closure callback) {
36 if (!base::WorkerPool::PostTaskAndReply(FROM_HERE,
37 base::Bind(&ScreenshotData::EncodeOnWorker,
46 scoped_refptr<base::RefCountedBytes> data() const { return data_; }
49 friend class base::RefCountedThreadSafe<ScreenshotData>;
50 virtual ~ScreenshotData() {
53 void EncodeOnWorker(const SkBitmap& bitmap) {
54 std::vector<unsigned char> data;
55 // Paint |bitmap| to a kA8_Config SkBitmap
57 a8Bitmap.setConfig(SkBitmap::kA8_Config,
61 a8Bitmap.allocPixels();
62 SkCanvas canvas(a8Bitmap);
64 SkColorFilter* filter = SkLumaColorFilter::Create();
65 paint.setColorFilter(filter);
67 canvas.drawBitmap(bitmap, SK_Scalar1, SK_Scalar1, &paint);
68 // Encode the a8Bitmap to grayscale PNG treating alpha as color intensity
69 if (gfx::PNGCodec::EncodeA8SkBitmap(a8Bitmap, &data))
70 data_ = new base::RefCountedBytes(data);
73 scoped_refptr<base::RefCountedBytes> data_;
75 DISALLOW_COPY_AND_ASSIGN(ScreenshotData);
78 NavigationEntryScreenshotManager::NavigationEntryScreenshotManager(
79 NavigationControllerImpl* owner)
81 screenshot_factory_(this),
82 min_screenshot_interval_ms_(kMinScreenshotIntervalMS) {
85 NavigationEntryScreenshotManager::~NavigationEntryScreenshotManager() {
88 void NavigationEntryScreenshotManager::TakeScreenshot() {
89 static bool overscroll_enabled = CommandLine::ForCurrentProcess()->
90 GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
91 if (!overscroll_enabled)
94 NavigationEntryImpl* entry =
95 NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry());
99 RenderViewHost* render_view_host =
100 owner_->delegate()->GetRenderViewHost();
101 if (!static_cast<RenderViewHostImpl*>
102 (render_view_host)->overscroll_controller()) {
105 content::RenderWidgetHostView* view = render_view_host->GetView();
109 // Make sure screenshots aren't taken too frequently.
110 base::Time now = base::Time::Now();
111 if (now - last_screenshot_time_ <
112 base::TimeDelta::FromMilliseconds(min_screenshot_interval_ms_)) {
116 last_screenshot_time_ = now;
118 TakeScreenshotImpl(render_view_host, entry);
121 // Implemented here and not in NavigationEntry because this manager keeps track
122 // of the total number of screen shots across all entries.
123 void NavigationEntryScreenshotManager::ClearAllScreenshots() {
124 int count = owner_->GetEntryCount();
125 for (int i = 0; i < count; ++i) {
126 ClearScreenshot(NavigationEntryImpl::FromNavigationEntry(
127 owner_->GetEntryAtIndex(i)));
129 DCHECK_EQ(GetScreenshotCount(), 0);
132 void NavigationEntryScreenshotManager::TakeScreenshotImpl(
133 RenderViewHost* host,
134 NavigationEntryImpl* entry) {
135 DCHECK(host && host->GetView());
137 host->CopyFromBackingStore(gfx::Rect(),
138 host->GetView()->GetViewBounds().size(),
139 base::Bind(&NavigationEntryScreenshotManager::OnScreenshotTaken,
140 screenshot_factory_.GetWeakPtr(),
141 entry->GetUniqueID()));
144 void NavigationEntryScreenshotManager::SetMinScreenshotIntervalMS(
146 DCHECK_GE(interval_ms, 0);
147 min_screenshot_interval_ms_ = interval_ms;
150 void NavigationEntryScreenshotManager::OnScreenshotTaken(int unique_id,
152 const SkBitmap& bitmap) {
153 NavigationEntryImpl* entry = NULL;
154 int entry_count = owner_->GetEntryCount();
155 for (int i = 0; i < entry_count; ++i) {
156 NavigationEntry* iter = owner_->GetEntryAtIndex(i);
157 if (iter->GetUniqueID() == unique_id) {
158 entry = NavigationEntryImpl::FromNavigationEntry(iter);
164 LOG(ERROR) << "Invalid entry with unique id: " << unique_id;
168 if (!success || bitmap.empty() || bitmap.isNull()) {
169 if (!ClearScreenshot(entry))
170 OnScreenshotSet(entry);
174 scoped_refptr<ScreenshotData> screenshot = new ScreenshotData();
175 screenshot->EncodeScreenshot(
177 base::Bind(&NavigationEntryScreenshotManager::OnScreenshotEncodeComplete,
178 screenshot_factory_.GetWeakPtr(),
183 int NavigationEntryScreenshotManager::GetScreenshotCount() const {
184 int screenshot_count = 0;
185 int entry_count = owner_->GetEntryCount();
186 for (int i = 0; i < entry_count; ++i) {
187 NavigationEntryImpl* entry =
188 NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(i));
189 if (entry->screenshot().get())
192 return screenshot_count;
195 void NavigationEntryScreenshotManager::OnScreenshotEncodeComplete(
197 scoped_refptr<ScreenshotData> screenshot) {
198 NavigationEntryImpl* entry = NULL;
199 int entry_count = owner_->GetEntryCount();
200 for (int i = 0; i < entry_count; ++i) {
201 NavigationEntry* iter = owner_->GetEntryAtIndex(i);
202 if (iter->GetUniqueID() == unique_id) {
203 entry = NavigationEntryImpl::FromNavigationEntry(iter);
209 entry->SetScreenshotPNGData(screenshot->data());
210 OnScreenshotSet(entry);
213 void NavigationEntryScreenshotManager::OnScreenshotSet(
214 NavigationEntryImpl* entry) {
215 if (entry->screenshot().get())
216 PurgeScreenshotsIfNecessary();
219 bool NavigationEntryScreenshotManager::ClearScreenshot(
220 NavigationEntryImpl* entry) {
221 if (!entry->screenshot().get())
224 entry->SetScreenshotPNGData(NULL);
228 void NavigationEntryScreenshotManager::PurgeScreenshotsIfNecessary() {
229 // Allow only a certain number of entries to keep screenshots.
230 const int kMaxScreenshots = 10;
231 int screenshot_count = GetScreenshotCount();
232 if (screenshot_count < kMaxScreenshots)
235 const int current = owner_->GetCurrentEntryIndex();
236 const int num_entries = owner_->GetEntryCount();
237 int available_slots = kMaxScreenshots;
238 if (NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(current))
239 ->screenshot().get()) {
243 // Keep screenshots closer to the current navigation entry, and purge the ones
244 // that are farther away from it. So in each step, look at the entries at
245 // each offset on both the back and forward history, and start counting them
246 // to make sure that the correct number of screenshots are kept in memory.
247 // Note that it is possible for some entries to be missing screenshots (e.g.
248 // when taking the screenshot failed for some reason). So there may be a state
249 // where there are a lot of entries in the back history, but none of them has
250 // any screenshot. In such cases, keep the screenshots for |kMaxScreenshots|
251 // entries in the forward history list.
252 int back = current - 1;
253 int forward = current + 1;
254 while (available_slots > 0 && (back >= 0 || forward < num_entries)) {
256 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
257 owner_->GetEntryAtIndex(back));
258 if (entry->screenshot().get())
263 if (available_slots > 0 && forward < num_entries) {
264 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
265 owner_->GetEntryAtIndex(forward));
266 if (entry->screenshot().get())
272 // Purge any screenshot at |back| or lower indices, and |forward| or higher
274 while (screenshot_count > kMaxScreenshots && back >= 0) {
275 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
276 owner_->GetEntryAtIndex(back));
277 if (ClearScreenshot(entry))
282 while (screenshot_count > kMaxScreenshots && forward < num_entries) {
283 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
284 owner_->GetEntryAtIndex(forward));
285 if (ClearScreenshot(entry))
289 CHECK_GE(screenshot_count, 0);
290 CHECK_LE(screenshot_count, kMaxScreenshots);
293 } // namespace content