navigation_entry_screenshot_manager.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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.
4
5#include "content/browser/frame_host/navigation_entry_screenshot_manager.h"
6
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/overscroll_configuration.h"
13#include "content/public/browser/render_widget_host.h"
14#include "content/public/browser/render_widget_host_view.h"
15#include "content/public/common/content_switches.h"
16#include "third_party/skia/include/core/SkCanvas.h"
17#include "third_party/skia/include/core/SkPaint.h"
18#include "third_party/skia/include/effects/SkLumaColorFilter.h"
19#include "ui/gfx/codec/png_codec.h"
20
21namespace {
22
23// Minimum delay between taking screenshots.
24const int kMinScreenshotIntervalMS = 1000;
25
26}
27
28namespace content {
29
30// Converts SkBitmap to grayscale and encodes to PNG data in a worker thread.
31class ScreenshotData : public base::RefCountedThreadSafe<ScreenshotData> {
32 public:
33  ScreenshotData() {
34  }
35
36  void EncodeScreenshot(const SkBitmap& bitmap, base::Closure callback) {
37    if (!base::WorkerPool::PostTaskAndReply(FROM_HERE,
38            base::Bind(&ScreenshotData::EncodeOnWorker,
39                       this,
40                       bitmap),
41            callback,
42            true)) {
43      callback.Run();
44    }
45  }
46
47  scoped_refptr<base::RefCountedBytes> data() const { return data_; }
48
49 private:
50  friend class base::RefCountedThreadSafe<ScreenshotData>;
51  virtual ~ScreenshotData() {
52  }
53
54  void EncodeOnWorker(const SkBitmap& bitmap) {
55    std::vector<unsigned char> data;
56    // Paint |bitmap| to a kAlpha_8_SkColorType SkBitmap
57    SkBitmap a8Bitmap;
58    a8Bitmap.allocPixels(SkImageInfo::MakeA8(bitmap.width(), bitmap.height()));
59    SkCanvas canvas(a8Bitmap);
60    SkPaint paint;
61    SkColorFilter* filter = SkLumaColorFilter::Create();
62    paint.setColorFilter(filter);
63    filter->unref();
64    canvas.drawBitmap(bitmap, SkIntToScalar(0), SkIntToScalar(0), &paint);
65    // Encode the a8Bitmap to grayscale PNG treating alpha as color intensity
66    if (gfx::PNGCodec::EncodeA8SkBitmap(a8Bitmap, &data))
67      data_ = new base::RefCountedBytes(data);
68  }
69
70  scoped_refptr<base::RefCountedBytes> data_;
71
72  DISALLOW_COPY_AND_ASSIGN(ScreenshotData);
73};
74
75NavigationEntryScreenshotManager::NavigationEntryScreenshotManager(
76    NavigationControllerImpl* owner)
77    : owner_(owner),
78      screenshot_factory_(this),
79      min_screenshot_interval_ms_(kMinScreenshotIntervalMS) {
80}
81
82NavigationEntryScreenshotManager::~NavigationEntryScreenshotManager() {
83}
84
85void NavigationEntryScreenshotManager::TakeScreenshot() {
86  static bool overscroll_enabled = CommandLine::ForCurrentProcess()->
87      GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
88  if (!overscroll_enabled)
89    return;
90
91  NavigationEntryImpl* entry =
92      NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry());
93  if (!entry)
94    return;
95
96  if (!owner_->delegate()->CanOverscrollContent())
97    return;
98
99  RenderViewHost* render_view_host =
100      owner_->delegate()->GetRenderViewHost();
101  content::RenderWidgetHostView* view = render_view_host->GetView();
102  if (!view)
103    return;
104
105  // Make sure screenshots aren't taken too frequently.
106  base::Time now = base::Time::Now();
107  if (now - last_screenshot_time_ <
108          base::TimeDelta::FromMilliseconds(min_screenshot_interval_ms_)) {
109    return;
110  }
111
112  last_screenshot_time_ = now;
113
114  TakeScreenshotImpl(render_view_host, entry);
115}
116
117// Implemented here and not in NavigationEntry because this manager keeps track
118// of the total number of screen shots across all entries.
119void NavigationEntryScreenshotManager::ClearAllScreenshots() {
120  int count = owner_->GetEntryCount();
121  for (int i = 0; i < count; ++i) {
122    ClearScreenshot(NavigationEntryImpl::FromNavigationEntry(
123        owner_->GetEntryAtIndex(i)));
124  }
125  DCHECK_EQ(GetScreenshotCount(), 0);
126}
127
128void NavigationEntryScreenshotManager::TakeScreenshotImpl(
129    RenderViewHost* host,
130    NavigationEntryImpl* entry) {
131  DCHECK(host && host->GetView());
132  DCHECK(entry);
133  SkColorType preferred_format = host->PreferredReadbackFormat();
134  host->CopyFromBackingStore(
135      gfx::Rect(),
136      host->GetView()->GetViewBounds().size(),
137      base::Bind(&NavigationEntryScreenshotManager::OnScreenshotTaken,
138                 screenshot_factory_.GetWeakPtr(),
139                 entry->GetUniqueID()),
140      preferred_format);
141}
142
143void NavigationEntryScreenshotManager::SetMinScreenshotIntervalMS(
144    int interval_ms) {
145  DCHECK_GE(interval_ms, 0);
146  min_screenshot_interval_ms_ = interval_ms;
147}
148
149void NavigationEntryScreenshotManager::OnScreenshotTaken(int unique_id,
150                                                     bool success,
151                                                     const SkBitmap& bitmap) {
152  NavigationEntryImpl* entry = NULL;
153  int entry_count = owner_->GetEntryCount();
154  for (int i = 0; i < entry_count; ++i) {
155    NavigationEntry* iter = owner_->GetEntryAtIndex(i);
156    if (iter->GetUniqueID() == unique_id) {
157      entry = NavigationEntryImpl::FromNavigationEntry(iter);
158      break;
159    }
160  }
161
162  if (!entry) {
163    LOG(ERROR) << "Invalid entry with unique id: " << unique_id;
164    return;
165  }
166
167  if (!success || bitmap.empty() || bitmap.isNull()) {
168    if (!ClearScreenshot(entry))
169      OnScreenshotSet(entry);
170    return;
171  }
172
173  scoped_refptr<ScreenshotData> screenshot = new ScreenshotData();
174  screenshot->EncodeScreenshot(
175      bitmap,
176      base::Bind(&NavigationEntryScreenshotManager::OnScreenshotEncodeComplete,
177                 screenshot_factory_.GetWeakPtr(),
178                 unique_id,
179                 screenshot));
180}
181
182int NavigationEntryScreenshotManager::GetScreenshotCount() const {
183  int screenshot_count = 0;
184  int entry_count = owner_->GetEntryCount();
185  for (int i = 0; i < entry_count; ++i) {
186    NavigationEntryImpl* entry =
187        NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(i));
188    if (entry->screenshot().get())
189      screenshot_count++;
190  }
191  return screenshot_count;
192}
193
194void NavigationEntryScreenshotManager::OnScreenshotEncodeComplete(
195    int unique_id,
196    scoped_refptr<ScreenshotData> screenshot) {
197  NavigationEntryImpl* entry = NULL;
198  int entry_count = owner_->GetEntryCount();
199  for (int i = 0; i < entry_count; ++i) {
200    NavigationEntry* iter = owner_->GetEntryAtIndex(i);
201    if (iter->GetUniqueID() == unique_id) {
202      entry = NavigationEntryImpl::FromNavigationEntry(iter);
203      break;
204    }
205  }
206  if (!entry)
207    return;
208  entry->SetScreenshotPNGData(screenshot->data());
209  OnScreenshotSet(entry);
210}
211
212void NavigationEntryScreenshotManager::OnScreenshotSet(
213    NavigationEntryImpl* entry) {
214  if (entry->screenshot().get())
215    PurgeScreenshotsIfNecessary();
216}
217
218bool NavigationEntryScreenshotManager::ClearScreenshot(
219    NavigationEntryImpl* entry) {
220  if (!entry->screenshot().get())
221    return false;
222
223  entry->SetScreenshotPNGData(NULL);
224  return true;
225}
226
227void NavigationEntryScreenshotManager::PurgeScreenshotsIfNecessary() {
228  // Allow only a certain number of entries to keep screenshots.
229  const int kMaxScreenshots = 10;
230  int screenshot_count = GetScreenshotCount();
231  if (screenshot_count < kMaxScreenshots)
232    return;
233
234  const int current = owner_->GetCurrentEntryIndex();
235  const int num_entries = owner_->GetEntryCount();
236  int available_slots = kMaxScreenshots;
237  if (NavigationEntryImpl::FromNavigationEntry(owner_->GetEntryAtIndex(current))
238          ->screenshot().get()) {
239    --available_slots;
240  }
241
242  // Keep screenshots closer to the current navigation entry, and purge the ones
243  // that are farther away from it. So in each step, look at the entries at
244  // each offset on both the back and forward history, and start counting them
245  // to make sure that the correct number of screenshots are kept in memory.
246  // Note that it is possible for some entries to be missing screenshots (e.g.
247  // when taking the screenshot failed for some reason). So there may be a state
248  // where there are a lot of entries in the back history, but none of them has
249  // any screenshot. In such cases, keep the screenshots for |kMaxScreenshots|
250  // entries in the forward history list.
251  int back = current - 1;
252  int forward = current + 1;
253  while (available_slots > 0 && (back >= 0 || forward < num_entries)) {
254    if (back >= 0) {
255      NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
256          owner_->GetEntryAtIndex(back));
257      if (entry->screenshot().get())
258        --available_slots;
259      --back;
260    }
261
262    if (available_slots > 0 && forward < num_entries) {
263      NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
264          owner_->GetEntryAtIndex(forward));
265      if (entry->screenshot().get())
266        --available_slots;
267      ++forward;
268    }
269  }
270
271  // Purge any screenshot at |back| or lower indices, and |forward| or higher
272  // indices.
273  while (screenshot_count > kMaxScreenshots && back >= 0) {
274    NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
275        owner_->GetEntryAtIndex(back));
276    if (ClearScreenshot(entry))
277      --screenshot_count;
278    --back;
279  }
280
281  while (screenshot_count > kMaxScreenshots && forward < num_entries) {
282    NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
283        owner_->GetEntryAtIndex(forward));
284    if (ClearScreenshot(entry))
285      --screenshot_count;
286    ++forward;
287  }
288  CHECK_GE(screenshot_count, 0);
289  CHECK_LE(screenshot_count, kMaxScreenshots);
290}
291
292}  // namespace content
293