navigation_entry_screenshot_manager.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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 kA8_Config SkBitmap
57    SkBitmap a8Bitmap;
58    a8Bitmap.setConfig(SkBitmap::kA8_Config,
59                       bitmap.width(),
60                       bitmap.height(),
61                       0);
62    a8Bitmap.allocPixels();
63    SkCanvas canvas(a8Bitmap);
64    SkPaint paint;
65    SkColorFilter* filter = SkLumaColorFilter::Create();
66    paint.setColorFilter(filter);
67    filter->unref();
68    canvas.drawBitmap(bitmap, SkIntToScalar(0), SkIntToScalar(0), &paint);
69    // Encode the a8Bitmap to grayscale PNG treating alpha as color intensity
70    if (gfx::PNGCodec::EncodeA8SkBitmap(a8Bitmap, &data))
71      data_ = new base::RefCountedBytes(data);
72  }
73
74  scoped_refptr<base::RefCountedBytes> data_;
75
76  DISALLOW_COPY_AND_ASSIGN(ScreenshotData);
77};
78
79NavigationEntryScreenshotManager::NavigationEntryScreenshotManager(
80    NavigationControllerImpl* owner)
81    : owner_(owner),
82      screenshot_factory_(this),
83      min_screenshot_interval_ms_(kMinScreenshotIntervalMS) {
84}
85
86NavigationEntryScreenshotManager::~NavigationEntryScreenshotManager() {
87}
88
89void NavigationEntryScreenshotManager::TakeScreenshot() {
90  static bool overscroll_enabled = CommandLine::ForCurrentProcess()->
91      GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
92  if (!overscroll_enabled)
93    return;
94
95  NavigationEntryImpl* entry =
96      NavigationEntryImpl::FromNavigationEntry(owner_->GetLastCommittedEntry());
97  if (!entry)
98    return;
99
100  if (!owner_->delegate()->CanOverscrollContent())
101    return;
102
103  RenderViewHost* render_view_host =
104      owner_->delegate()->GetRenderViewHost();
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