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